Skip to main content

scsidisk/scsidvd/
mod.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use super::AsyncScsiDisk;
5use super::SavedSenseData;
6use super::ScsiResult;
7use super::ScsiSaveRestore;
8use super::scsi;
9use crate::Request;
10use crate::ScsiSavedState;
11use crate::SenseDataSlot;
12use disk_backend::Disk;
13use disk_backend::DiskError;
14use guestmem::AccessError;
15use guestmem::MemoryRead;
16use guestmem::MemoryWrite;
17use parking_lot::Mutex;
18use parking_lot::RwLock;
19use scsi::AdditionalSenseCode;
20use scsi::FeatureNumber::FeaturePowerManagement;
21use scsi::FeatureNumber::FeatureRealTimeStreaming;
22use scsi::FeatureNumber::FeatureTimeout;
23use scsi::ScsiOp;
24use scsi::ScsiStatus;
25use scsi::SenseData;
26use scsi::SenseKey;
27use scsi::srb::SrbStatus;
28use scsi_buffers::RequestBuffers;
29use scsi_core::save_restore::DriveState;
30use scsi_core::save_restore::IsoMediumEvent;
31use scsi_core::save_restore::ScsiDvdSavedState;
32use stackfuture::StackFuture;
33use std::sync::Arc;
34use thiserror::Error;
35use tracing::Instrument;
36use vmcore::save_restore::RestoreError;
37use vmcore::save_restore::SaveError;
38use zerocopy::FromBytes;
39use zerocopy::FromZeros;
40use zerocopy::Immutable;
41use zerocopy::IntoBytes;
42use zerocopy::KnownLayout;
43
44enum Media {
45    Unloaded,
46    Loaded(Disk),
47}
48
49pub struct SimpleScsiDvd {
50    media: Arc<RwLock<Media>>,
51    media_state: Arc<Mutex<MediaState>>,
52    sense_data: SenseDataSlot,
53}
54
55#[derive(Debug, Default)]
56struct MediaState {
57    drive_state: DriveState,
58    pending_medium_event: IsoMediumEvent,
59    persistent: bool,
60    prevent: bool,
61}
62
63#[derive(Error, Debug)]
64enum ScsiDvdError {
65    #[error("illegal request, asc: {0:?}, qualifier: {1:?}")]
66    IllegalRequest(AdditionalSenseCode, u8),
67    #[error("data overrun")]
68    DataOverrun,
69    #[error("memory access error")]
70    MemoryAccess(#[source] AccessError),
71    #[error("io error")]
72    IoError(#[source] DiskError),
73    #[error("not ready, sense key: {0:?}, qualifier: {1}")]
74    SenseNotReady(AdditionalSenseCode, u8),
75    #[error("invalid request - no sense data")]
76    IllegalRequestNoSenseData,
77}
78
79struct RequestParametersIso {
80    tx: usize,
81    offset: u64,
82}
83
84impl AsyncScsiDisk for SimpleScsiDvd {
85    fn clear_nexus_state(&self) {
86        self.sense_data.set(None);
87        let mut media_state = self.media_state.lock();
88        // The tray lock is the one that bites: an initiator that set PREVENT and
89        // then went away would otherwise leave the next one unable to eject, its
90        // START STOP UNIT failed with MEDIUM_REMOVAL_PREVENTED by a decision it
91        // never made.
92        media_state.prevent = false;
93        media_state.persistent = false;
94        // Queued for an initiator that is gone. The next one learns the current
95        // media state from its own TEST UNIT READY or GET EVENT STATUS.
96        media_state.pending_medium_event = IsoMediumEvent::None;
97        // drive_state and the media itself are deliberately untouched: an ejected
98        // tray stays ejected, as it would on a real drive.
99    }
100
101    fn execute_scsi<'a>(
102        &'a self,
103        external_data: &'a RequestBuffers<'a>,
104        request: &'a Request,
105    ) -> StackFuture<'a, ScsiResult, { super::ASYNC_SCSI_DISK_STACK_SIZE }> {
106        StackFuture::from(async move {
107            let op = request.scsiop();
108
109            let sector_count = self.sector_count();
110            let result = match op {
111                ScsiOp::INQUIRY => self.handle_inquiry_iso(external_data, request),
112                ScsiOp::TEST_UNIT_READY => self.handle_test_unit_ready_iso(),
113                ScsiOp::READ | ScsiOp::READ12 | ScsiOp::READ16 => {
114                    self.handle_data_cdb_async_iso(external_data, request, sector_count)
115                        .instrument(tracing::trace_span!("handle_data_cdb_async", ?op,))
116                        .await
117                }
118                ScsiOp::GET_EVENT_STATUS => self.handle_get_event_status(external_data, request),
119                ScsiOp::GET_CONFIGURATION => {
120                    self.handle_get_configuration_iso(external_data, request)
121                }
122                ScsiOp::READ_CAPACITY => self.handle_read_capacity_iso(external_data, sector_count),
123                ScsiOp::READ_TOC => self.handle_read_toc(external_data, request),
124                ScsiOp::START_STOP_UNIT => self.handle_start_stop_unit(request).await,
125                ScsiOp::MODE_SENSE10 => self.handle_mode_sense_iso(external_data, request),
126                ScsiOp::REQUEST_SENSE => self.handle_request_sense_iso(external_data, request),
127                ScsiOp::MEDIUM_REMOVAL => self.handle_medium_removal_iso(request),
128                ScsiOp::MODE_SELECT10 => self.handle_mode_select_iso(request, external_data),
129                ScsiOp::READ_TRACK_INFORMATION => {
130                    self.handle_read_track_information_iso(external_data, request)
131                }
132                ScsiOp::READ_DVD_STRUCTURE => {
133                    self.handle_read_dvd_structure(external_data, request)
134                }
135                ScsiOp::GET_PERFORMANCE => self.handle_get_performance(external_data, request),
136                ScsiOp::MECHANISM_STATUS => self.handle_mechanism_status(external_data, request),
137                ScsiOp::READ_BUFFER_CAPACITY => {
138                    self.handle_read_buffer_capacity(external_data, request)
139                }
140                ScsiOp::READ_DISC_INFORMATION => {
141                    self.handle_read_disc_information(external_data, request)
142                }
143                ScsiOp::SET_STREAMING => self.handle_set_streaming(external_data, request),
144                _ => {
145                    tracelimit::warn_ratelimited!(op = ?op, "illegal command");
146                    Err(ScsiDvdError::IllegalRequest(
147                        AdditionalSenseCode::ILLEGAL_COMMAND,
148                        0,
149                    ))
150                }
151            };
152
153            self.process_result(result, op)
154        })
155    }
156}
157
158impl inspect::Inspect for SimpleScsiDvd {
159    fn inspect(&self, req: inspect::Request<'_>) {
160        let mut resp = req.respond();
161
162        resp.field("drive_state", self.media_state.lock().drive_state);
163
164        if let Media::Loaded(disk) = &*self.media.read() {
165            resp.field("backend", disk);
166        }
167    }
168}
169
170const ISO_SECTOR_SIZE: u32 = 2048;
171
172impl SimpleScsiDvd {
173    pub fn new(disk: Option<Disk>) -> Self {
174        assert!(disk.as_ref().is_none() || disk.as_ref().unwrap().sector_size() <= ISO_SECTOR_SIZE);
175
176        let (media, pending_medium_event, drive_state) = if let Some(disk) = disk {
177            (
178                RwLock::new(Media::Loaded(disk)).into(),
179                IsoMediumEvent::MediaToMedia,
180                DriveState::MediumPresentTrayClosed,
181            )
182        } else {
183            (
184                RwLock::new(Media::Unloaded).into(),
185                IsoMediumEvent::MediaToNoMedia,
186                DriveState::MediumNotPresentTrayClosed,
187            )
188        };
189
190        SimpleScsiDvd {
191            media,
192            media_state: Arc::new(Mutex::new(MediaState {
193                pending_medium_event,
194                drive_state,
195                ..Default::default()
196            })),
197            sense_data: SenseDataSlot::default(),
198        }
199    }
200
201    pub fn change_media(&self, disk: Option<Disk>) {
202        if let Some(disk) = disk {
203            // Insert medium
204            let mut media = self.media.write();
205
206            let mut media_state = self.media_state.lock();
207            media_state.drive_state = DriveState::MediumPresentTrayOpen;
208            media_state.pending_medium_event = IsoMediumEvent::NoMediaToMedia;
209
210            *media = Media::Loaded(disk);
211
212            tracing::debug!("completed host initiated insert");
213        } else {
214            // Eject medium
215            let mut media = self.media.write();
216            let mut media_state = self.media_state.lock();
217
218            // This will cause the next GESN or TUR command to report medium removal
219            media_state.drive_state = DriveState::MediumNotPresentTrayOpen;
220            media_state.pending_medium_event = IsoMediumEvent::MediaToNoMedia;
221
222            *media = Media::Unloaded;
223
224            tracing::debug!("completed host initiated eject");
225        }
226    }
227
228    fn sector_shift(&self) -> u8 {
229        ISO_SECTOR_SIZE.trailing_zeros() as u8
230    }
231
232    fn sector_count(&self) -> u64 {
233        match &*self.media.read() {
234            Media::Unloaded => 0,
235            Media::Loaded(disk) => disk.sector_count() / self.balancer(),
236        }
237    }
238
239    fn balancer(&self) -> u64 {
240        match &*self.media.read() {
241            Media::Unloaded => unreachable!("cannot read/write from unloaded disk"),
242            Media::Loaded(disk) => {
243                // Per protocol, the sector size for an ISO is 2048. Any read request which
244                // goes to backend disk with sector size <= 2048 then we need to convert
245                // read offset and sector count accordingly.
246                // e.g.: If disk backend has sector_size = 512 then a read request at sector n
247                // should actually go to sector 4*n of backend disk. If sector count of
248                // backend device is n then it should be reported as sector count/4 to the guest.
249                (ISO_SECTOR_SIZE / disk.sector_size()) as u64
250            }
251        }
252    }
253}
254
255impl ScsiSaveRestore for SimpleScsiDvd {
256    fn save(&self) -> Result<Option<ScsiSavedState>, SaveError> {
257        let sense = self.sense_data.get();
258        let sense_data = sense.map(|sense| SavedSenseData {
259            sense_key: sense.header.sense_key.0,
260            additional_sense_code: sense.additional_sense_code.0,
261            additional_sense_code_qualifier: sense.additional_sense_code_qualifier,
262        });
263
264        let media_state = self.media_state.lock();
265        Ok(Some(ScsiSavedState::ScsiDvd(ScsiDvdSavedState {
266            sense_data,
267            persistent: media_state.persistent,
268            prevent: media_state.prevent,
269            drive_state: media_state.drive_state,
270            pending_medium_event: media_state.pending_medium_event,
271        })))
272    }
273
274    fn restore(&self, state: &ScsiSavedState) -> Result<(), RestoreError> {
275        if let ScsiSavedState::ScsiDvd(dvd_state) = state {
276            let ScsiDvdSavedState {
277                sense_data,
278                persistent,
279                prevent,
280                drive_state,
281                pending_medium_event,
282            } = *dvd_state;
283
284            // restore sense data
285            self.sense_data.set(
286                sense_data
287                    .map(|sense| {
288                        SenseData::new(
289                            SenseKey(sense.sense_key),
290                            AdditionalSenseCode(sense.additional_sense_code),
291                            sense.additional_sense_code_qualifier,
292                        )
293                    })
294                    .as_ref(),
295            );
296
297            let mut media_state = self.media_state.lock();
298            media_state.drive_state = drive_state;
299            media_state.pending_medium_event = pending_medium_event;
300            media_state.persistent = persistent;
301            media_state.prevent = prevent;
302            Ok(())
303        } else {
304            Err(RestoreError::InvalidSavedState(anyhow::anyhow!(
305                "saved state didn't match expected format ScsiDvdSavedState"
306            )))
307        }
308    }
309}
310
311/// Writes a VPD page.
312///
313/// Assumes that allocation_length is already validated to be at least
314/// `size_of::<scsi::VpdPageHeader>()`.
315fn write_vpd_page<T: ?Sized + IntoBytes + Immutable + KnownLayout>(
316    external_data: &RequestBuffers<'_>,
317    allocation_length: usize,
318    page_code: u8,
319    page_data: &T,
320) -> Result<usize, ScsiDvdError> {
321    let header = scsi::VpdPageHeader {
322        device_type: scsi::READ_ONLY_DIRECT_ACCESS_DEVICE,
323        page_code,
324        reserved: 0,
325        page_length: size_of_val(page_data).try_into().unwrap(),
326    };
327
328    let tx = std::cmp::min(
329        allocation_length,
330        size_of_val(&header) + size_of_val(page_data),
331    );
332
333    let mut writer = external_data.writer();
334    writer
335        .write(header.as_bytes())
336        .map_err(ScsiDvdError::MemoryAccess)?;
337
338    writer
339        .write(&page_data.as_bytes()[..tx - size_of_val(&header)])
340        .map_err(ScsiDvdError::MemoryAccess)?;
341
342    Ok(tx)
343}
344
345impl SimpleScsiDvd {
346    fn handle_inquiry_iso(
347        &self,
348        external_data: &RequestBuffers<'_>,
349        request: &Request,
350    ) -> Result<usize, ScsiDvdError> {
351        let cdb = scsi::CdbInquiry::read_from_prefix(&request.cdb[..])
352            .unwrap()
353            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
354
355        let allocation_length = cdb.allocation_length.get() as usize;
356
357        if external_data.len() < allocation_length {
358            return Err(ScsiDvdError::DataOverrun);
359        }
360        // If the PAGE CODE field is not set to zero when the EVPD bit is set to zero,
361        // the command shall be terminated with CHECK CONDITION status, with the sense key
362        // set to ILLEGAL REQUEST, and the additional sense code set to INVALID FIELD IN CDB.
363        let enable_vpd = cdb.flags.vpd();
364        if cdb.page_code != 0 && !enable_vpd {
365            return Err(ScsiDvdError::IllegalRequest(
366                AdditionalSenseCode::INVALID_CDB,
367                0,
368            ));
369        }
370
371        if enable_vpd {
372            if allocation_length < size_of::<scsi::VpdPageHeader>() {
373                return Err(ScsiDvdError::DataOverrun);
374            }
375
376            match cdb.page_code {
377                scsi::VPD_SUPPORTED_PAGES => {
378                    self.handle_vpd_supported_pages_iso(external_data, allocation_length)
379                }
380                scsi::VPD_DEVICE_IDENTIFIERS => {
381                    self.handle_vpd_device_identifiers_iso(external_data, allocation_length)
382                }
383                _ => Err(ScsiDvdError::IllegalRequest(
384                    AdditionalSenseCode::INVALID_CDB,
385                    0,
386                )),
387            }
388        } else {
389            self.handle_no_vpd_page_iso(external_data, allocation_length)
390        }
391    }
392
393    fn handle_test_unit_ready_iso(&self) -> Result<usize, ScsiDvdError> {
394        let drive_state = self.media_state.lock().drive_state;
395
396        match drive_state {
397            DriveState::MediumPresentTrayOpen => Ok(0),
398            DriveState::MediumPresentTrayClosed => Ok(0),
399            DriveState::MediumNotPresentTrayOpen => Err(ScsiDvdError::SenseNotReady(
400                AdditionalSenseCode::NO_MEDIA_IN_DEVICE,
401                scsi::MEDIUM_NOT_PRESENT_TRAY_OPEN,
402            )),
403            DriveState::MediumNotPresentTrayClosed => Err(ScsiDvdError::SenseNotReady(
404                AdditionalSenseCode::NO_MEDIA_IN_DEVICE,
405                scsi::MEDIUM_NOT_PRESENT_TRAY_CLOSED,
406            )),
407        }
408    }
409
410    async fn handle_data_cdb_async_iso(
411        &self,
412        external_data: &RequestBuffers<'_>,
413        request: &Request,
414        sector_count: u64,
415    ) -> Result<usize, ScsiDvdError> {
416        let op = request.scsiop();
417        let p = self.validate_data_cdb_iso(external_data, request, sector_count, op)?;
418
419        if p.tx != 0 {
420            let mut media = None;
421            if let Media::Loaded(disk) = &*self.media.read() {
422                media = Some(disk.clone());
423            }
424
425            if let Some(disk) = media {
426                disk.read_vectored(&external_data.subrange(0, p.tx), p.offset)
427                    .await
428                    .map_err(ScsiDvdError::IoError)?;
429            }
430        }
431
432        Ok(p.tx)
433    }
434
435    fn handle_get_event_status(
436        &self,
437        external_data: &RequestBuffers<'_>,
438        request: &Request,
439    ) -> Result<usize, ScsiDvdError> {
440        let cdb = scsi::CdbGetEventStatusNotification::read_from_prefix(&request.cdb[..])
441            .unwrap()
442            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
443        let allocation_length = cdb.event_list_length.get() as usize;
444        let mut pending_medium_event = IsoMediumEvent::None;
445
446        //  Make sure buffer is as big as the requested size
447        if allocation_length > external_data.len() {
448            return Err(ScsiDvdError::DataOverrun);
449        }
450
451        // don't support asynchronous mode
452        if !cdb.flags.immediate() {
453            return Err(ScsiDvdError::IllegalRequest(
454                AdditionalSenseCode::INVALID_CDB,
455                0x00,
456            ));
457        }
458
459        // Go through the possible event notifications in priority order.
460        // only one event class should be reported at once.
461        // if no event has occurred, the driver shall report the "no change"
462        // event for the highest priority requested event class.
463        let mut media_status = scsi::NotificationMediaStatus::new_zeroed();
464        {
465            let mut media_state = self.media_state.lock();
466            if (cdb.notification_class_request & scsi::NOTIFICATION_MEDIA_STATUS_CLASS_MASK) != 0 {
467                pending_medium_event = media_state.pending_medium_event;
468                match media_state.pending_medium_event {
469                    IsoMediumEvent::None => (),
470                    IsoMediumEvent::NoMediaToMedia => {
471                        media_status.media_event = scsi::NOTIFICATION_MEDIA_EVENT_NEW_MEDIA;
472                        media_status.status_info.set_media_present(true);
473
474                        media_state.drive_state = DriveState::MediumPresentTrayClosed;
475                        media_state.pending_medium_event = IsoMediumEvent::None;
476                    }
477                    IsoMediumEvent::MediaToNoMedia => {
478                        media_status.media_event = scsi::NOTIFICATION_MEDIA_EVENT_MEDIA_REMOVAL;
479                        media_status.status_info.set_media_present(false);
480
481                        media_state.pending_medium_event = IsoMediumEvent::None;
482                    }
483                    IsoMediumEvent::MediaToMedia => {
484                        media_status.media_event = scsi::NOTIFICATION_MEDIA_EVENT_MEDIA_REMOVAL;
485                        media_status.status_info.set_media_present(false);
486
487                        media_state.drive_state = DriveState::MediumNotPresentTrayOpen;
488                        media_state.pending_medium_event = IsoMediumEvent::NoMediaToMedia;
489                    }
490                }
491
492                media_status
493                    .status_info
494                    .set_door_tray_open(media_state.drive_state.tray_open());
495            }
496        }
497
498        let data_transfer_length = if pending_medium_event != IsoMediumEvent::None {
499            let header_size = self.iso_init_event_header(
500                external_data,
501                allocation_length,
502                (size_of::<scsi::NotificationEventStatusHeader>() + size_of_val(&media_status)
503                    - size_of::<u16>()) as u16,
504                false,
505                scsi::NOTIFICATION_MEDIA_STATUS_CLASS_EVENTS,
506            )?;
507
508            let body_size =
509                std::cmp::min(size_of_val(&media_status), allocation_length - header_size);
510
511            external_data
512                .subrange(header_size, external_data.len() - header_size)
513                .writer()
514                .write(&media_status.as_bytes()[..body_size])
515                .map_err(ScsiDvdError::MemoryAccess)?;
516
517            if media_status.media_event == scsi::NOTIFICATION_MEDIA_EVENT_NEW_MEDIA {
518                tracing::info!("media arrival");
519            } else if media_status.media_event == scsi::NOTIFICATION_MEDIA_EVENT_MEDIA_REMOVAL {
520                tracing::info!("media removal");
521            }
522            header_size + body_size
523        } else {
524            if (cdb.notification_class_request & scsi::NOTIFICATION_OPERATIONAL_CHANGE_CLASS_MASK)
525                != 0
526            {
527                let persistent_prevented = self.media_state.lock().persistent;
528                let operational_status = scsi::NotificationOperationalStatus {
529                    operation_event: scsi::NOTIFICATION_POWER_EVENT_NO_CHANGE,
530                    flags: scsi::OperationalStatusFlags::new()
531                        .with_operational_status(scsi::NOTIFICATION_OPERATIONAL_STATUS_AVAILABLE)
532                        .with_reserved2(0x00)
533                        .with_persistent_prevented(persistent_prevented),
534                    operation: 0.into(),
535                };
536
537                let header_size = self.iso_init_event_header(
538                    external_data,
539                    allocation_length,
540                    (size_of::<scsi::NotificationEventStatusHeader>()
541                        + size_of_val(&operational_status)
542                        - size_of::<u16>()) as u16,
543                    false,
544                    scsi::NOTIFICATION_OPERATIONAL_CHANGE_CLASS_EVENTS,
545                )?;
546
547                let body_size = std::cmp::min(
548                    size_of_val(&operational_status),
549                    allocation_length - header_size,
550                );
551                external_data
552                    .subrange(header_size, external_data.len() - header_size)
553                    .writer()
554                    .write(&operational_status.as_bytes()[..body_size])
555                    .map_err(ScsiDvdError::MemoryAccess)?;
556
557                header_size + body_size
558            } else if (cdb.notification_class_request
559                & scsi::NOTIFICATION_POWER_MANAGEMENT_CLASS_MASK)
560                != 0
561            {
562                let header_size = self.iso_init_event_header(
563                    external_data,
564                    allocation_length,
565                    (size_of::<scsi::NotificationEventStatusHeader>()
566                        + size_of::<scsi::NotificationPowerStatus>()
567                        - size_of::<u16>()) as u16,
568                    false,
569                    scsi::NOTIFICATION_POWER_MANAGEMENT_CLASS_EVENTS,
570                )?;
571                let data = scsi::NotificationPowerStatus {
572                    power_event: scsi::NOTIFICATION_POWER_EVENT_NO_CHANGE,
573                    power_status: scsi::NOTIFICATION_POWER_STATUS_ACTIVE,
574                    reserved: [0x00, 0x00],
575                };
576                let body_size = std::cmp::min(
577                    size_of::<scsi::NotificationPowerStatus>(),
578                    allocation_length - header_size,
579                );
580                external_data
581                    .subrange(header_size, external_data.len() - header_size)
582                    .writer()
583                    .write(&data.as_bytes()[..body_size])
584                    .map_err(ScsiDvdError::MemoryAccess)?;
585
586                header_size + body_size
587            } else if (cdb.notification_class_request
588                & scsi::NOTIFICATION_EXTERNAL_REQUEST_CLASS_MASK)
589                != 0
590            {
591                let persistent_prevented = self.media_state.lock().persistent;
592                let external_status = scsi::NotificationExternalStatus {
593                    external_event: scsi::NOTIFICATION_EXTERNAL_EVENT_NO_CHANGE,
594                    flags: scsi::ExternalStatusFlags::new()
595                        .with_external_status(scsi::NOTIFICATION_EXTERNAL_STATUS_READY)
596                        .with_reserved2(0x00)
597                        .with_persistent_prevented(persistent_prevented),
598                    reserved: [0x00, 0x00],
599                };
600
601                let header_size = self.iso_init_event_header(
602                    external_data,
603                    allocation_length,
604                    (size_of::<scsi::NotificationEventStatusHeader>()
605                        + size_of_val(&external_status)
606                        - size_of::<u16>()) as u16,
607                    false,
608                    scsi::NOTIFICATION_EXTERNAL_REQUEST_CLASS_EVENTS,
609                )?;
610                let body_size = std::cmp::min(
611                    size_of_val(&external_status),
612                    allocation_length - header_size,
613                );
614                external_data
615                    .subrange(header_size, external_data.len() - header_size)
616                    .writer()
617                    .write(&external_status.as_bytes()[..body_size])
618                    .map_err(ScsiDvdError::MemoryAccess)?;
619
620                header_size + body_size
621            } else if (cdb.notification_class_request & scsi::NOTIFICATION_MULTI_HOST_CLASS_MASK)
622                != 0
623            {
624                let header_size = self.iso_init_event_header(
625                    external_data,
626                    allocation_length,
627                    (size_of::<scsi::NotificationEventStatusHeader>()
628                        + size_of::<scsi::NotificationMultiHostStatus>()
629                        - size_of::<u16>()) as u16,
630                    false,
631                    scsi::NOTIFICATION_MULTI_HOST_CLASS_EVENTS,
632                )?;
633                let multi_host_status = scsi::NotificationMultiHostStatus {
634                    multi_host_event: scsi::NOTIFICATION_MULTI_HOST_EVENT_NO_CHANGE,
635                    flags: scsi::MultiHostStatusFlags::new()
636                        .with_multi_host_status(scsi::NOTIFICATION_MULTI_HOST_STATUS_READY)
637                        .with_reserved2(0x00)
638                        .with_persistent_prevented(self.media_state.lock().persistent),
639                    priority: 0.into(),
640                };
641                let body_size = std::cmp::min(
642                    size_of_val(&multi_host_status),
643                    allocation_length - header_size,
644                );
645                external_data
646                    .subrange(header_size, external_data.len() - header_size)
647                    .writer()
648                    .write(&multi_host_status.as_bytes()[..body_size])
649                    .map_err(ScsiDvdError::MemoryAccess)?;
650
651                header_size + body_size
652            } else if (cdb.notification_class_request & scsi::NOTIFICATION_MEDIA_STATUS_CLASS_MASK)
653                != 0
654            {
655                let header_size = self.iso_init_event_header(
656                    external_data,
657                    allocation_length,
658                    (size_of::<scsi::NotificationEventStatusHeader>() + size_of_val(&media_status)
659                        - size_of::<u16>()) as u16,
660                    false,
661                    scsi::NOTIFICATION_MEDIA_STATUS_CLASS_EVENTS,
662                )?;
663                media_status
664                    .status_info
665                    .set_door_tray_open(self.media_state.lock().drive_state.tray_open());
666                media_status
667                    .status_info
668                    .set_media_present(self.media_state.lock().drive_state.medium_present());
669                let datab: &[u8] = media_status.as_bytes();
670                let body_size =
671                    std::cmp::min(size_of_val(&media_status), allocation_length - header_size);
672                external_data
673                    .subrange(header_size, external_data.len() - header_size)
674                    .writer()
675                    .write(&datab[..body_size])
676                    .map_err(ScsiDvdError::MemoryAccess)?;
677                header_size + body_size
678            } else if (cdb.notification_class_request & scsi::NOTIFICATION_DEVICE_BUSY_CLASS_MASK)
679                != 0
680            {
681                let header_size = self.iso_init_event_header(
682                    external_data,
683                    allocation_length,
684                    (size_of::<scsi::NotificationEventStatusHeader>()
685                        + size_of::<scsi::NotificationBusyStatus>()
686                        - size_of::<u16>()) as u16,
687                    false,
688                    scsi::NOTIFICATION_DEVICE_BUSY_CLASS_EVENTS,
689                )?;
690                let data = scsi::NotificationBusyStatus {
691                    device_busy_event: scsi::NOTIFICATION_BUSY_EVENT_NO_EVENT,
692                    device_busy_status: scsi::NOTIFICATION_BUSY_STATUS_NO_EVENT,
693                    time: 0.into(),
694                };
695                let body_size = std::cmp::min(size_of_val(&data), allocation_length - header_size);
696                external_data
697                    .subrange(header_size, external_data.len() - header_size)
698                    .writer()
699                    .write(&data.as_bytes()[..body_size])
700                    .map_err(ScsiDvdError::MemoryAccess)?;
701
702                header_size + body_size
703            } else {
704                self.iso_init_event_header(
705                    external_data,
706                    allocation_length,
707                    (size_of::<scsi::NotificationEventStatusHeader>() - size_of::<u16>()) as u16,
708                    false,
709                    scsi::NOTIFICATION_NO_CLASS_EVENTS,
710                )?
711            }
712        };
713
714        Ok(data_transfer_length)
715    }
716
717    fn handle_get_configuration_iso(
718        &self,
719        external_data: &RequestBuffers<'_>,
720        request: &Request,
721    ) -> Result<usize, ScsiDvdError> {
722        let cdb = scsi::CdbGetConfiguration::read_from_prefix(&request.cdb[..])
723            .unwrap()
724            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
725        let request_type = cdb.flags.request_type();
726        let starting_feature = cdb.starting_feature.get() as usize;
727        let allocation_length = cdb.allocation_length.get() as usize;
728        let mut feature_size: usize = 0;
729
730        if allocation_length > external_data.len() {
731            return Err(ScsiDvdError::DataOverrun);
732        }
733        let mut bytes_used =
734            std::cmp::min(allocation_length, size_of::<scsi::GetConfigurationHeader>());
735        let header_size = bytes_used;
736        let mut bytes_req = size_of::<scsi::GetConfigurationHeader>();
737        match request_type {
738            scsi::RequestType::ALL => {
739                // Return all the Feature Descriptors Supported
740                for i in 0..scsi::LIST_OF_FEATURES.len() {
741                    if (scsi::LIST_OF_FEATURES[i] as usize) >= starting_feature {
742                        bytes_used += self.iso_get_feature_page(
743                            &external_data.subrange(bytes_used, external_data.len() - bytes_used),
744                            allocation_length - bytes_used,
745                            scsi::LIST_OF_FEATURES[i],
746                            false,
747                            &mut feature_size,
748                        )?;
749                        bytes_req += feature_size;
750                    }
751                }
752
753                // data_length field indicates the amount of data available given a sufficient allocation length
754                // following this field. Thus the size of itself should be excluded.
755                self.iso_init_configuration_header(
756                    &external_data.subrange(0, header_size),
757                    bytes_req,
758                )?;
759            }
760            scsi::RequestType::CURRENT => {
761                // Return all the Feature Descriptors Supported
762                for i in 0..scsi::LIST_OF_FEATURES.len() {
763                    if (scsi::LIST_OF_FEATURES[i] as usize) >= starting_feature {
764                        bytes_used += self.iso_get_feature_page(
765                            &external_data.subrange(bytes_used, external_data.len() - bytes_used),
766                            allocation_length - bytes_used,
767                            scsi::LIST_OF_FEATURES[i],
768                            true,
769                            &mut feature_size,
770                        )?;
771                        bytes_req += feature_size;
772                    }
773                }
774
775                // data_length field indicates the amount of data available given a sufficient allocation length
776                // following this field. Thus the size of itself should be excluded.
777                self.iso_init_configuration_header(
778                    &external_data.subrange(0, header_size),
779                    bytes_req,
780                )?;
781            }
782
783            scsi::RequestType::ONE => {
784                // Return only the Feature Descriptor that has been requested
785                bytes_used += self.iso_get_feature_page(
786                    &external_data.subrange(bytes_used, external_data.len() - bytes_used),
787                    allocation_length - bytes_used,
788                    scsi::FeatureNumber::try_from(starting_feature).unwrap(),
789                    false,
790                    &mut feature_size,
791                )?;
792
793                bytes_req += feature_size;
794
795                self.iso_init_configuration_header(
796                    &external_data.subrange(0, header_size),
797                    bytes_req,
798                )?;
799            }
800            _ => return Err(ScsiDvdError::IllegalRequestNoSenseData),
801        }
802
803        Ok(bytes_used)
804    }
805
806    fn handle_read_capacity_iso(
807        &self,
808        external_data: &RequestBuffers<'_>,
809        sector_count: u64,
810    ) -> Result<usize, ScsiDvdError> {
811        let tx = size_of::<scsi::ReadCapacityData>();
812
813        // media is no longer accessible
814        self.validate_media_for_read()?;
815
816        let last_lba = std::cmp::min(sector_count - 1, u32::MAX.into());
817        let data = scsi::ReadCapacityData {
818            logical_block_address: (last_lba as u32).into(),
819            bytes_per_block: ISO_SECTOR_SIZE.into(),
820        };
821
822        external_data
823            .writer()
824            .write(&data.as_bytes()[..tx])
825            .map_err(ScsiDvdError::MemoryAccess)?;
826
827        Ok(tx)
828    }
829
830    fn handle_read_toc(
831        &self,
832        external_data: &RequestBuffers<'_>,
833        request: &Request,
834    ) -> Result<usize, ScsiDvdError> {
835        let cdb = scsi::CdbReadToc::read_from_prefix(&request.cdb[..])
836            .unwrap()
837            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
838        let allocation_length = cdb.allocation_length.get() as usize;
839        let format = cdb.format2 & 0x0f;
840        let msf = cdb.flag1.msf();
841        let mut formatted_toc = scsi::ReadTocFormattedToc {
842            length: 0x0802.into(),
843            first_complete_session: 0x01,
844            last_complete_session: 0x01,
845            track1: scsi::TrackData {
846                reserved: 0x00,
847                flag: scsi::TrackDataFlag::new().with_control(0x4).with_adr(0x1),
848                track_number: 0x01,
849                reserved1: 0x00,
850                address: [0; 4],
851            },
852            trackaa: scsi::TrackData {
853                reserved: 0x00,
854                flag: scsi::TrackDataFlag::new().with_control(0x4).with_adr(0x1),
855                track_number: 0xaa,
856                reserved1: 0x00,
857                address: [0; 4],
858            },
859        };
860        let mut session_data = scsi::CdromTocSessionData {
861            length: 0x000a.into(),
862            first_complete_session: 0x01,
863            last_complete_session: 0x01,
864            track_data: scsi::TrackData {
865                reserved: 0x00,
866                flag: scsi::TrackDataFlag::new().with_control(0x4).with_adr(0x1),
867                track_number: 0x01,
868                reserved1: 0x00,
869                address: [0; 4],
870            },
871        };
872        let data_transfer_length: usize;
873
874        self.validate_media_for_read()?;
875
876        let last_lba = std::cmp::min(self.sector_count() - 1, u32::MAX.into());
877        if allocation_length > external_data.len() {
878            return Err(ScsiDvdError::DataOverrun);
879        }
880        match format {
881            scsi::CDROM_READ_TOC_EX_FORMAT_TOC => {
882                if msf {
883                    formatted_toc.track1.address = [0x00, 0x00, 0x02, 0x00];
884                    formatted_toc.trackaa.address = [
885                        0x00,
886                        (((last_lba + 150) / 75) / 60) as u8,
887                        (((last_lba + 150) / 75) % 60) as u8,
888                        ((last_lba + 150) % 75) as u8,
889                    ]
890                } else {
891                    formatted_toc.track1.address = [0x00, 0x00, 0x00, 0x00];
892                    formatted_toc.trackaa.address = [
893                        (last_lba >> 24) as u8,
894                        (last_lba >> 16) as u8,
895                        (last_lba >> 8) as u8,
896                        last_lba as u8,
897                    ]
898                }
899                data_transfer_length =
900                    std::cmp::min(allocation_length, size_of::<scsi::ReadTocFormattedToc>());
901                external_data
902                    .writer()
903                    .write(&formatted_toc.as_bytes()[..data_transfer_length])
904                    .map_err(ScsiDvdError::MemoryAccess)?;
905            }
906            scsi::CDROM_READ_TOC_EX_FORMAT_SESSION => {
907                if msf {
908                    session_data.track_data.address = [0x00, 0x00, 0x02, 0x00];
909                } else {
910                    session_data.track_data.address = [0x00, 0x00, 0x00, 0x00];
911                }
912                data_transfer_length =
913                    std::cmp::min(allocation_length, size_of::<scsi::CdromTocSessionData>());
914                external_data
915                    .writer()
916                    .write(&session_data.as_bytes()[..data_transfer_length])
917                    .map_err(ScsiDvdError::MemoryAccess)?;
918            }
919            _ => {
920                return Err(ScsiDvdError::IllegalRequest(
921                    AdditionalSenseCode::INVALID_CDB,
922                    0x00,
923                ));
924            }
925        }
926        Ok(data_transfer_length)
927    }
928
929    async fn handle_start_stop_unit(&self, request: &Request) -> Result<usize, ScsiDvdError> {
930        let cdb = scsi::StartStop::read_from_prefix(&request.cdb[..])
931            .unwrap()
932            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
933        let start = cdb.flag.start();
934        let load_eject = cdb.flag.load_eject();
935
936        match (load_eject, start) {
937            (false, false) => (), // stop the disc, do nothing here
938            (false, true) => (),  // start the disc and make ready for access, do nothing here
939            (true, false) => {
940                let mut previous_media = None;
941                {
942                    let mut media_state = self.media_state.lock();
943                    // eject the disc if permitted
944                    if !media_state.prevent {
945                        if media_state.drive_state == DriveState::MediumPresentTrayClosed {
946                            let mut media = self.media.write();
947
948                            // This will cause the next GESN or TUR command to report medium removal
949                            media_state.pending_medium_event = IsoMediumEvent::MediaToNoMedia;
950
951                            previous_media = Some(std::mem::replace(&mut *media, Media::Unloaded));
952
953                            tracing::debug!("handling guest initiated eject");
954                        }
955
956                        media_state.drive_state = DriveState::MediumNotPresentTrayOpen;
957                    } else if media_state.drive_state.medium_present() {
958                        return Err(ScsiDvdError::IllegalRequest(
959                            AdditionalSenseCode::MEDIUM_REMOVAL_PREVENTED,
960                            0x02,
961                        ));
962                    } else {
963                        return Err(ScsiDvdError::SenseNotReady(
964                            AdditionalSenseCode::MEDIUM_REMOVAL_PREVENTED,
965                            0x02,
966                        ));
967                    }
968                }
969                if let Some(media) = previous_media {
970                    if let Media::Loaded(disk) = media {
971                        if let Err(e) = disk.eject().await {
972                            tracelimit::error_ratelimited!(error = ?e, "eject error");
973                        } else {
974                            tracing::debug!("guest initiated eject complete");
975                        }
976                    } else {
977                        tracelimit::warn_ratelimited!("attempted to eject unloaded media");
978                    }
979                }
980            }
981            (true, true) => {
982                let mut media_state = self.media_state.lock();
983                if media_state.drive_state.tray_open() {
984                    // Once the drive is ejected through SCSI, the media is permanently removed.
985                    media_state.drive_state = DriveState::MediumNotPresentTrayClosed;
986                }
987            }
988        }
989
990        Ok(0)
991    }
992
993    fn handle_request_sense_iso(
994        &self,
995        external_data: &RequestBuffers<'_>,
996        request: &Request,
997    ) -> Result<usize, ScsiDvdError> {
998        let cdb = scsi::CdbRequestSense::read_from_prefix(&request.cdb[..])
999            .unwrap()
1000            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
1001        let allocation_length = cdb.allocation_length as usize;
1002        let new_sense_data =
1003            SenseData::new(SenseKey::NO_SENSE, AdditionalSenseCode::NO_SENSE, 0x00);
1004
1005        self.sense_data.set(Some(&new_sense_data));
1006
1007        if external_data.len() < allocation_length {
1008            Err(ScsiDvdError::DataOverrun)
1009        } else {
1010            let tx = std::cmp::min(allocation_length, size_of::<SenseData>());
1011            external_data
1012                .writer()
1013                .write(&new_sense_data.as_bytes()[..tx])
1014                .map_err(ScsiDvdError::MemoryAccess)?;
1015            Ok(tx)
1016        }
1017    }
1018
1019    fn handle_mode_sense_iso(
1020        &self,
1021        external_data: &RequestBuffers<'_>,
1022        request: &Request,
1023    ) -> Result<usize, ScsiDvdError> {
1024        let cdb = scsi::ModeSense10::read_from_prefix(&request.cdb[..])
1025            .unwrap()
1026            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
1027        let page_code = cdb.flags2.page_code();
1028        let allocation_length = cdb.allocation_length.get() as usize;
1029        let pc = cdb.flags2.pc() << 6;
1030
1031        let mut bytes_used = std::cmp::min(allocation_length, super::MODE_PARAMETER_HEADER10_SIZE);
1032        let buffer_size_header = bytes_used;
1033        let mut bytes_required = super::MODE_PARAMETER_HEADER10_SIZE as u16;
1034        let mut mode_page_size: u16 = 0;
1035
1036        if external_data.len() < allocation_length {
1037            return Err(ScsiDvdError::DataOverrun);
1038        }
1039
1040        if pc == scsi::MODE_SENSE_SAVED_VALUES {
1041            return Err(ScsiDvdError::IllegalRequest(
1042                AdditionalSenseCode::SAVING_PARAMETER_NOT_SUPPORTED,
1043                0,
1044            ));
1045        }
1046
1047        // Copy as much data from the mode page table as the remaining buffer.
1048        // If remaining buffer is big enough to copy the whole page, copy
1049        // the whole page length.
1050        match page_code {
1051            scsi::MODE_PAGE_ERROR_RECOVERY
1052            | scsi::MODE_PAGE_POWER_CONDITION
1053            | scsi::MODE_PAGE_CDVD_INACTIVITY => {
1054                bytes_used += self
1055                    .iso_get_mode_page(
1056                        &external_data.subrange(bytes_used, external_data.len() - bytes_used),
1057                        allocation_length - bytes_used,
1058                        page_code,
1059                        &mut mode_page_size,
1060                    )
1061                    .unwrap();
1062                bytes_required += mode_page_size;
1063                self.iso_init_mode_sense_header(
1064                    &external_data.subrange(0, super::MODE_PARAMETER_HEADER10_SIZE),
1065                    allocation_length,
1066                    bytes_required,
1067                )?;
1068                Ok(bytes_used)
1069            }
1070            scsi::MODE_SENSE_RETURN_ALL => {
1071                for i in 0..scsi::LIST_OF_MODE_PAGES.len() {
1072                    bytes_used += self
1073                        .iso_get_mode_page(
1074                            &external_data.subrange(bytes_used, external_data.len() - bytes_used),
1075                            allocation_length - bytes_used,
1076                            scsi::LIST_OF_MODE_PAGES[i],
1077                            &mut mode_page_size,
1078                        )
1079                        .unwrap();
1080                    bytes_required += mode_page_size;
1081                }
1082                self.iso_init_mode_sense_header(
1083                    &external_data.subrange(0, buffer_size_header),
1084                    allocation_length,
1085                    bytes_required,
1086                )?;
1087                Ok(bytes_used)
1088            }
1089            _ => Err(ScsiDvdError::IllegalRequest(
1090                AdditionalSenseCode::INVALID_CDB,
1091                0,
1092            )),
1093        }
1094    }
1095
1096    fn handle_medium_removal_iso(&self, request: &Request) -> Result<usize, ScsiDvdError> {
1097        let cdb = scsi::CdbMediaRemoval::read_from_prefix(&request.cdb[..])
1098            .unwrap()
1099            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
1100
1101        // prevent/allow media removal based on the Persistent/Prevent bits
1102        let mut media_state = self.media_state.lock();
1103        media_state.persistent = cdb.flags.persistent();
1104        media_state.prevent = cdb.flags.prevent();
1105        Ok(0)
1106    }
1107
1108    fn handle_mode_select_iso(
1109        &self,
1110        request: &Request,
1111        external_data: &RequestBuffers<'_>,
1112    ) -> Result<usize, ScsiDvdError> {
1113        let cdb = scsi::ModeSelect10::read_from_prefix(&request.cdb[..])
1114            .unwrap()
1115            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
1116
1117        let sp_bit = cdb.flags.spbit();
1118        let request_length = cdb.parameter_list_length.get() as usize;
1119        if request_length == 0 {
1120            return Ok(0);
1121        }
1122
1123        if request_length > external_data.len() {
1124            return Err(ScsiDvdError::DataOverrun);
1125        }
1126
1127        // If the parameter list length results in the truncation of any mode parameter header,
1128        // mode parameter block descriptor(s), or mode page, then the command shall be terminated
1129        // with CHECK CONDITION status, with the sense key set to ILLEGAL REQUEST, and the
1130        // additional sense code set to PARAMETER LIST LENGTH ERROR.
1131        if request_length < super::MODE_PARAMETER_HEADER10_SIZE {
1132            return Err(ScsiDvdError::IllegalRequest(
1133                AdditionalSenseCode::PARAMETER_LIST_LENGTH,
1134                0x00,
1135            ));
1136        }
1137
1138        // Don't support saving pages.
1139        if sp_bit {
1140            return Err(ScsiDvdError::IllegalRequest(
1141                AdditionalSenseCode::INVALID_CDB,
1142                0x00,
1143            ));
1144        }
1145
1146        if request_length == super::MODE_PARAMETER_HEADER10_SIZE {
1147            return Ok(0);
1148        }
1149
1150        // Parse pages.
1151        // All 3 mode pages supported have the same length
1152        if request_length
1153            < super::MODE_PARAMETER_HEADER10_SIZE + size_of::<scsi::ModeReadWriteRecoveryPage>()
1154        {
1155            return Err(ScsiDvdError::IllegalRequest(
1156                AdditionalSenseCode::PARAMETER_LIST_LENGTH,
1157                0x00,
1158            ));
1159        }
1160
1161        let mut buffer: Vec<u8> = vec![0; request_length];
1162        external_data
1163            .reader()
1164            .read(&mut buffer)
1165            .map_err(ScsiDvdError::MemoryAccess)?;
1166
1167        let mode_page_error_recovery = scsi::ModeReadWriteRecoveryPage::read_from_prefix(
1168            &buffer[super::MODE_PARAMETER_HEADER10_SIZE
1169                ..super::MODE_PARAMETER_HEADER10_SIZE
1170                    + size_of::<scsi::ModeReadWriteRecoveryPage>()],
1171        )
1172        .unwrap()
1173        .0; // TODO: zerocopy: from-prefix (read_from_prefix): use-rest-of-range, zerocopy: err (https://github.com/microsoft/openvmm/issues/759)
1174        match mode_page_error_recovery.page_code {
1175            scsi::MODE_PAGE_ERROR_RECOVERY => {
1176                // ModePageErrorRecovery = (PMODE_READ_WRITE_RECOVERY_PAGE) (Buffer + sizeof (MODE_PARAMETER_HEADER10));
1177
1178                // parse Read/Write error recovery mode page
1179                Ok(0)
1180            }
1181            scsi::MODE_PAGE_POWER_CONDITION => {
1182                // ModePagePowerCondition = (PPOWER_CONDITION_PAGE) (Buffer + sizeof (MODE_PARAMETER_HEADER10));
1183
1184                // parse power condition mode page
1185                Ok(0)
1186            }
1187            scsi::MODE_PAGE_CDVD_INACTIVITY => {
1188                // ModePageTimeoutProtect = (PMODE_SENSE_MODE_PAGE_TIMEOUT_PROTECT) (Buffer + sizeof (MODE_PARAMETER_HEADER10));
1189
1190                // parse timeout and protect mode page
1191                Ok(0)
1192            }
1193            _ => Err(ScsiDvdError::IllegalRequestNoSenseData),
1194        }
1195    }
1196
1197    fn handle_read_track_information_iso(
1198        &self,
1199        external_data: &RequestBuffers<'_>,
1200        request: &Request,
1201    ) -> Result<usize, ScsiDvdError> {
1202        let cdb = scsi::CdbReadTrackInformation::read_from_prefix(&request.cdb[..])
1203            .unwrap()
1204            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
1205        let number_type = cdb.flag.number_type();
1206        let open = cdb.flag.open();
1207        let logical_track_number = cdb.logical_track_number.get() as usize;
1208        let allocation_length = cdb.allocation_length.get() as usize;
1209
1210        self.validate_media_for_read()?;
1211        let last_lba = std::cmp::min(self.sector_count() - 1, u32::MAX.into()) as u32;
1212
1213        if allocation_length > external_data.len() {
1214            return Err(ScsiDvdError::DataOverrun);
1215        }
1216
1217        // If it is not possible for the currently mounted disc to have open tracks, and Open is set to one,
1218        // then the command shall be terminated with CHECK CONDITION status and sense bytes SK/ASC/ASCQ
1219        // shall be set to ILLEGAL REQUEST/ INVALID FIELD IN CDB.
1220        if open {
1221            return Err(ScsiDvdError::IllegalRequest(
1222                AdditionalSenseCode::INVALID_CDB,
1223                0x00,
1224            ));
1225        }
1226
1227        match number_type {
1228            0x0 => {
1229                // MAX = Last Possible Lead-out Start Address as returned by the READ DISC INFORMATION
1230                // command (0x00). If LBA >= MAX, the command shall be terminated with CHECK CONDITION status
1231                // and sense bytes SK/ASC/ ASCQ shall be set to ILLEGAL REQUEST/LOGICAL BLOCK ADDRESS OUT OF RANGE.
1232                return Err(ScsiDvdError::IllegalRequest(
1233                    AdditionalSenseCode::ILLEGAL_BLOCK,
1234                    0x00,
1235                ));
1236            }
1237            0x1 => {
1238                if logical_track_number != 1 {
1239                    // If the currently mounted disc is not CD, the command shall be terminated with CHECK CONDITION
1240                    // status and sense bytes SK/ASC/ASCQ shall be set to ILLEGAL REQUEST/INVALID FIELD IN CDB
1241                    //
1242                    // If TM is the Last Track Number in the Last Session as returned in READ DISC INFORMATION command
1243                    // Standard Disc Information. If LTN > TM, the command shall be terminated with CHECK CONDITION status
1244                    // and sense bytes SK/ASC/ASCQ shall be set to ILLEGAL REQUEST/INVALID FIELD IN CDB.
1245                    return Err(ScsiDvdError::IllegalRequest(
1246                        AdditionalSenseCode::INVALID_CDB,
1247                        0x00,
1248                    ));
1249                }
1250            }
1251            0x2 => {
1252                // SM is the Number of Sessions as returned by the READ DISC INFORMATION command. If LogicalTrackNumber > SM,
1253                // the command shall be terminated with CHECK CONDITION status and sense bytes SK/ASC/ASCQ shall be set to
1254                // ILLEGAL REQUEST/INVALID FIELD IN CDB.
1255                if logical_track_number != 1 {
1256                    return Err(ScsiDvdError::IllegalRequest(
1257                        AdditionalSenseCode::INVALID_CDB,
1258                        0x00,
1259                    ));
1260                }
1261            }
1262            _ => {
1263                return Err(ScsiDvdError::IllegalRequestNoSenseData);
1264            }
1265        }
1266        // Fill in logical track size
1267        let track_information = scsi::TrackInformation3 {
1268            length: 0x002e.into(),
1269            track_number_lsb: 0x01,
1270            session_number_lsb: 0x01,
1271            reserved: 0x00,
1272            track_mode: 0x04,
1273            /*
1274               UCHAR DataMode      : 4 = 0x1;
1275               UCHAR FixedPacket   : 1 = 0x0;
1276               UCHAR Packet        : 1 = 0x1;
1277               UCHAR Blank         : 1 = 0x0;
1278               UCHAR ReservedTrack : 1 = 0x0;
1279            */
1280            data_mode: 0b00100001,
1281            track_size: last_lba.into(),
1282            ..FromZeros::new_zeroed()
1283        };
1284        let tx = std::cmp::min(allocation_length, size_of::<scsi::TrackInformation3>());
1285        external_data
1286            .writer()
1287            .write(&track_information.as_bytes()[..tx])
1288            .map_err(ScsiDvdError::MemoryAccess)?;
1289        Ok(tx)
1290    }
1291
1292    fn handle_read_dvd_structure(
1293        &self,
1294        external_data: &RequestBuffers<'_>,
1295        request: &Request,
1296    ) -> Result<usize, ScsiDvdError> {
1297        let cdb = scsi::CdbReadDVDStructure::read_from_prefix(&request.cdb[..])
1298            .unwrap()
1299            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
1300        let media_type = cdb.media_type;
1301        let layer = cdb.layer;
1302        let allocation_length = cdb.allocation_length.get() as usize;
1303
1304        self.validate_media_for_read()?;
1305
1306        if allocation_length > external_data.len() {
1307            return Err(ScsiDvdError::DataOverrun);
1308        }
1309
1310        // only single layer DVD media type is supported
1311        if media_type != 0 {
1312            return Err(ScsiDvdError::IllegalRequest(
1313                AdditionalSenseCode::INVALID_MEDIA,
1314                scsi::SCSI_SENSEQ_INCOMPATIBLE_FORMAT,
1315            ));
1316        }
1317
1318        if layer != 0 {
1319            return Err(ScsiDvdError::IllegalRequestNoSenseData);
1320        }
1321
1322        match cdb.format {
1323            scsi::DVD_FORMAT_LEAD_IN => {
1324                let tx = std::cmp::min(
1325                    allocation_length,
1326                    size_of::<scsi::ReadDVDStructurePhysicalFormatInformation>(),
1327                );
1328                let data = scsi::ReadDVDStructurePhysicalFormatInformation {
1329                    length: 0x802.into(),
1330                    reserved: [0x00, 0x00],
1331                    reserved2: 0x00,    // Part Version = 0 & Disk Category = DVD-ROM
1332                    maximum_rate: 0x04, // Maximum Rate = 30.24 Mbps & Disc Size = 120 mm,
1333                    /*
1334                        Layer Type = layer contains embossed data
1335                        Track = PTP
1336                        Number of Layers = 1
1337                        Reserved
1338                    */
1339                    layer: 0x00,
1340                    reserved3: 0x00, // Track Density = 0.74 um/track & Linear Density = 0.267 um/bit,
1341                    reserved4: 0x00,
1342                    starting_physical_sector: [0x03, 0x00, 0x00],
1343                    reserved5: 0x00,
1344                    end_physical_sector: [0x00, 0x00, 0x00],
1345                    reserved6: 0x00,
1346                    end_physical_sector_in_layer0: [0x00, 0x00, 0x00],
1347                    bca: 0x00,
1348                };
1349                external_data
1350                    .writer()
1351                    .write(&data.as_bytes()[..tx])
1352                    .map_err(ScsiDvdError::MemoryAccess)?;
1353                Ok(tx)
1354            }
1355            scsi::DVD_FORMAT_COPYRIGHT => {
1356                let tx = std::cmp::min(
1357                    allocation_length,
1358                    size_of::<scsi::ReadDVDStructureCopyrightInformation>(),
1359                );
1360                let data = scsi::ReadDVDStructureCopyrightInformation {
1361                    data_length: 0x0006.into(),
1362                    reserved: 0x00,
1363                    copyright_protection_system: 0x00,
1364                    region_management_information: 0x00,
1365                    reserved2: [0x00, 0x00],
1366                };
1367                external_data
1368                    .writer()
1369                    .write(&data.as_bytes()[..tx])
1370                    .map_err(ScsiDvdError::MemoryAccess)?;
1371                Ok(tx)
1372            }
1373            scsi::DVD_FORMAT_BCA => {
1374                // It is not mandatory for DVD players to support reading the BCA,
1375                // but DVD-ROM drives should according to the Mount Fuji specification.
1376                // However, if a DVD media does not have BCA, the command should be terminated.
1377                Err(ScsiDvdError::IllegalRequestNoSenseData)
1378            }
1379            scsi::DVD_FORMAT_MANUFACTURING => {
1380                let tx = std::cmp::min(
1381                    allocation_length,
1382                    size_of::<scsi::ReadDVDStructureManufacturingStructure>(),
1383                );
1384                let data = scsi::ReadDVDStructureManufacturingStructure {
1385                    data_length: 0x0802.into(),
1386                    reserved: [0x00, 0x00],
1387                };
1388                external_data
1389                    .writer()
1390                    .write(&data.as_bytes()[..tx])
1391                    .map_err(ScsiDvdError::MemoryAccess)?;
1392                Ok(tx)
1393            }
1394            _ => Err(ScsiDvdError::IllegalRequest(
1395                AdditionalSenseCode::INVALID_CDB,
1396                0x00,
1397            )),
1398        }
1399    }
1400
1401    fn handle_get_performance(
1402        &self,
1403        external_data: &RequestBuffers<'_>,
1404        request: &Request,
1405    ) -> Result<usize, ScsiDvdError> {
1406        let cdb = scsi::CdbGetPerformance::read_from_prefix(&request.cdb[..])
1407            .unwrap()
1408            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
1409        let data_type = cdb.data_type;
1410        let write = cdb.flags.write();
1411        let tolerance = cdb.flags.tolerance();
1412        let except = cdb.flags.except();
1413        let mut nominal_performance = scsi::GetPerformanceNominalPerformanceDescriptor {
1414            start_lba: 0.into(),
1415            start_performance: scsi::PERFORMANCE_1000_BYTES_PER_SECOND.into(),
1416            end_lba: 0.into(),
1417            end_performance: scsi::PERFORMANCE_1000_BYTES_PER_SECOND.into(),
1418        };
1419
1420        let maximum_number_of_descriptors = cdb.maximum_number_of_descriptors.get();
1421        let mut data_transfer_length: u64;
1422
1423        self.validate_media_for_read()?;
1424
1425        let last_lba = std::cmp::min(self.sector_count() - 1, u32::MAX.into())
1426            .try_into()
1427            .expect("last_lba must be u32");
1428        match data_type {
1429            scsi::PERFORMANCE_TYPE_PERFORMANCE_DATA => {
1430                // only read performance data will be returned
1431                // 0x2 is the only defined tolerance value
1432                if write || (tolerance != 0x2) {
1433                    return Err(ScsiDvdError::IllegalRequestNoSenseData);
1434                }
1435                match except {
1436                    scsi::PERFORMANCE_EXCEPT_NOMINAL_PERFORMANCE => {
1437                        // Patch the end lba
1438                        nominal_performance.end_lba.set(last_lba);
1439                        // The Performance Data Length field shall specify the amount of result data excluding
1440                        // the Performance Data Length itself
1441                        let header_size = self.iso_init_performance_header(
1442                            external_data,
1443                            external_data.len().try_into().unwrap(),
1444                            (size_of::<scsi::GetPerformanceHeader>()
1445                                + size_of::<scsi::GetPerformanceNominalPerformanceDescriptor>()
1446                                - size_of::<u64>())
1447                            .try_into()
1448                            .unwrap(),
1449                            0,
1450                        )?;
1451                        data_transfer_length = header_size.try_into().unwrap();
1452                        if maximum_number_of_descriptors >= 1 {
1453                            let body_size = std::cmp::min(
1454                                external_data.len() - header_size,
1455                                size_of::<scsi::GetPerformanceNominalPerformanceDescriptor>(),
1456                            );
1457                            external_data
1458                                .subrange(header_size, external_data.len() - header_size)
1459                                .writer()
1460                                .write(&nominal_performance.as_bytes()[..body_size])
1461                                .map_err(ScsiDvdError::MemoryAccess)?;
1462                            data_transfer_length += body_size as u64;
1463                        }
1464                    }
1465                    scsi::PERFORMANCE_EXCEPT_ENTIRE_PERFORMANCE_LIST
1466                    | scsi::PERFORMANCE_EXCEPT_PERFORMANCE_EXCEPTIONS_ONLY => {
1467                        // The Performance Data Length field shall specify the amount of result data excluding
1468                        // the Performance Data Length itself
1469                        let header_size = self.iso_init_performance_header(
1470                            external_data,
1471                            external_data.len().try_into().unwrap(),
1472                            (size_of::<scsi::GetPerformanceHeader>() - size_of::<u64>())
1473                                .try_into()
1474                                .unwrap(),
1475                            1,
1476                        )?;
1477                        data_transfer_length = header_size.try_into().unwrap();
1478                    }
1479                    _ => {
1480                        return Err(ScsiDvdError::IllegalRequestNoSenseData);
1481                    }
1482                }
1483            }
1484            _ => {
1485                // don't support all other types
1486                return Err(ScsiDvdError::IllegalRequest(
1487                    AdditionalSenseCode::INVALID_CDB,
1488                    0x00,
1489                ));
1490            }
1491        }
1492        Ok(data_transfer_length as usize)
1493    }
1494
1495    fn handle_mechanism_status(
1496        &self,
1497        external_data: &RequestBuffers<'_>,
1498        request: &Request,
1499    ) -> Result<usize, ScsiDvdError> {
1500        let cdb = scsi::CdbMechStatus::read_from_prefix(&request.cdb[..])
1501            .unwrap()
1502            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
1503        let allocation_length = cdb.allocation_length.get() as usize;
1504
1505        if allocation_length > external_data.len() {
1506            return Err(ScsiDvdError::DataOverrun);
1507        }
1508
1509        // copy the standard header
1510        // since the device is a DVD-ROM, not a medium changer, no slot table will be returned
1511        let mechanism_status_header: scsi::MechanismStatusHeader = scsi::MechanismStatusHeader {
1512            flags: scsi::MechanismStatusHeaderFlags::new()
1513                .with_door_open(self.media_state.lock().drive_state.tray_open()),
1514            ..FromZeros::new_zeroed()
1515        };
1516        let tx = std::cmp::min(allocation_length, size_of::<scsi::MechanismStatusHeader>());
1517        external_data
1518            .writer()
1519            .write(&mechanism_status_header.as_bytes()[..tx])
1520            .map_err(ScsiDvdError::MemoryAccess)?;
1521
1522        Ok(tx)
1523    }
1524
1525    fn handle_read_buffer_capacity(
1526        &self,
1527        external_data: &RequestBuffers<'_>,
1528        request: &Request,
1529    ) -> Result<usize, ScsiDvdError> {
1530        let cdb = scsi::CdbReadBufferCapacity::read_from_prefix(&request.cdb[..])
1531            .unwrap()
1532            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
1533        let block_info = cdb.flags.block_info();
1534        let allocation_length = cdb.allocation_length.get() as usize;
1535
1536        if allocation_length > external_data.len() {
1537            return Err(ScsiDvdError::DataOverrun);
1538        }
1539
1540        let tx = std::cmp::min(allocation_length, size_of::<scsi::ReadBufferCapacityData>());
1541
1542        let data = scsi::ReadBufferCapacityData {
1543            data_length: (size_of::<scsi::ReadBufferCapacityData>() as u16 - 2).into(),
1544            block_data_returned: block_info as u8,
1545            ..FromZeros::new_zeroed()
1546        };
1547
1548        external_data
1549            .writer()
1550            .write(&data.as_bytes()[..tx])
1551            .map_err(ScsiDvdError::MemoryAccess)?;
1552
1553        Ok(tx)
1554    }
1555
1556    fn handle_read_disc_information(
1557        &self,
1558        external_data: &RequestBuffers<'_>,
1559        request: &Request,
1560    ) -> Result<usize, ScsiDvdError> {
1561        let cdb = scsi::CdbReadDiscInformation::read_from_prefix(&request.cdb[..])
1562            .unwrap()
1563            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
1564        let data_type = cdb.flags.data_type();
1565        let allocation_length = cdb.allocation_length.get() as usize;
1566
1567        self.validate_media_for_read()?;
1568        if allocation_length > external_data.len() {
1569            return Err(ScsiDvdError::DataOverrun);
1570        }
1571
1572        // only standard disc information is supported
1573        if data_type != 0x0 {
1574            return Err(ScsiDvdError::IllegalRequest(
1575                AdditionalSenseCode::INVALID_CDB,
1576                0x00,
1577            ));
1578        }
1579
1580        let tx = std::cmp::min(allocation_length, size_of::<scsi::DiscInformation>());
1581        let data = scsi::DiscInformation {
1582            length: 0x0020.into(),
1583            flags1: scsi::DiscInfoFlags1::new()
1584                .with_disc_status(0x2)
1585                .with_last_session_status(0x3)
1586                .with_erasable(false)
1587                .with_reserved1(0x0),
1588            first_track_number: 0x01,
1589            number_of_sessions_lsb: 0x01,
1590            last_session_first_track_lsb: 0x01,
1591            last_session_last_track_lsb: 0x01,
1592            flags2: scsi::DiscInfoFlags2::new()
1593                .with_mrw_status(0)
1594                .with_mrw_dirty_bit(false)
1595                .with_reserved2(0)
1596                .with_uru(true)
1597                .with_dbc_v(false)
1598                .with_did_v(false),
1599            ..FromZeros::new_zeroed()
1600        };
1601        external_data
1602            .writer()
1603            .write(&data.as_bytes()[..tx])
1604            .map_err(ScsiDvdError::MemoryAccess)?;
1605
1606        Ok(tx)
1607    }
1608
1609    fn handle_set_streaming(
1610        &self,
1611        external_data: &RequestBuffers<'_>,
1612        request: &Request,
1613    ) -> Result<usize, ScsiDvdError> {
1614        let cdb = scsi::CdbSetStreaming::read_from_prefix(&request.cdb[..])
1615            .unwrap()
1616            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
1617        let parameter_list_length = usize::from(cdb.parameter_list_length);
1618        let mut buffer = vec![0; parameter_list_length];
1619
1620        if parameter_list_length == 0 {
1621            return Ok(0);
1622        }
1623
1624        self.validate_media_for_read()?;
1625
1626        if parameter_list_length > external_data.len() {
1627            return Err(ScsiDvdError::DataOverrun);
1628        }
1629
1630        // If the Parameter List Length results in the truncation of Performance Descriptor,
1631        // the command shall be terminated with CHECK CONDITION status and sense bytes SK/ASC/ASCQ
1632        // shall be set to ILLEGAL REQUEST/PARAMETER LIST LENGTH ERROR.
1633        if parameter_list_length < size_of::<scsi::SetStreamingPerformanceDescriptor>() {
1634            return Err(ScsiDvdError::IllegalRequest(
1635                AdditionalSenseCode::PARAMETER_LIST_LENGTH,
1636                0x00,
1637            ));
1638        }
1639
1640        // process the command
1641        // just do sanity check, and success the command if nothing specified is wrong
1642        external_data
1643            .reader()
1644            .read(&mut buffer)
1645            .map_err(ScsiDvdError::MemoryAccess)?;
1646        let performance_descriptor =
1647            scsi::SetStreamingPerformanceDescriptor::read_from_prefix(&buffer[..])
1648                .unwrap()
1649                .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
1650
1651        // If RDD bit is set to one, it shall indicate that the drive is to return to its
1652        // default performance settings and the remaining fields in this descriptor shall be ignored.
1653        if performance_descriptor.flags.rdd() {
1654            return Ok(0);
1655        }
1656        let last_lba = std::cmp::min(self.sector_count() - 1, u32::MAX.into()) as u32;
1657
1658        if u32::from(performance_descriptor.start_lba) > last_lba
1659            || u32::from(performance_descriptor.end_lba) > last_lba
1660        {
1661            return Err(ScsiDvdError::IllegalRequestNoSenseData);
1662        }
1663
1664        Ok(0)
1665    }
1666}
1667
1668impl SimpleScsiDvd {
1669    fn iso_init_event_header(
1670        &self,
1671        external_data: &RequestBuffers<'_>,
1672        buffer_size: usize,
1673        event_data_length: u16,
1674        nea: bool,
1675        notification_class: u8,
1676    ) -> Result<usize, ScsiDvdError> {
1677        let mut header: scsi::NotificationEventStatusHeader =
1678            scsi::NotificationEventStatusHeader::new_zeroed();
1679        header.event_data_length.set(event_data_length);
1680        header.flags.set_nea(nea);
1681        header.flags.set_notification_class(notification_class);
1682        header.supported_event_classes = scsi::NOTIFICATION_OPERATIONAL_CHANGE_CLASS_MASK
1683            | scsi::NOTIFICATION_POWER_MANAGEMENT_CLASS_MASK
1684            | scsi::NOTIFICATION_EXTERNAL_REQUEST_CLASS_MASK
1685            | scsi::NOTIFICATION_MEDIA_STATUS_CLASS_MASK
1686            | scsi::NOTIFICATION_MULTI_HOST_CLASS_MASK
1687            | scsi::NOTIFICATION_DEVICE_BUSY_CLASS_MASK;
1688        let tx: usize = std::cmp::min(
1689            size_of::<scsi::NotificationEventStatusHeader>(),
1690            buffer_size,
1691        );
1692        external_data
1693            .writer()
1694            .write(&header.as_bytes()[..tx])
1695            .map_err(ScsiDvdError::MemoryAccess)?;
1696        Ok(tx)
1697    }
1698
1699    fn iso_get_feature_page(
1700        &self,
1701        external_data: &RequestBuffers<'_>,
1702        buffer_size: usize,
1703        feature_code: scsi::FeatureNumber,
1704        current_only: bool,
1705        feature_size: &mut usize,
1706    ) -> Result<usize, ScsiDvdError> {
1707        let mut bytes_used: usize = 0;
1708        let mut profile_list = scsi::GetConfigurationFeatureDataProfileList {
1709            header: scsi::FeatureHeader {
1710                feature_code: (scsi::FeatureNumber::FeatureProfileList as u16).into(),
1711                flags: scsi::FeatureHeaderFlags::new()
1712                    .with_current(true)
1713                    .with_persistent(true)
1714                    .with_version(0)
1715                    .with_reserved(0),
1716                additional_length: (2 * (size_of::<scsi::FeatureDataProfileList>() as u8)),
1717            },
1718            profile: [
1719                scsi::FeatureDataProfileList {
1720                    profile_number: scsi::PROFILE_DVD_ROM.into(),
1721                    current: 0x01,
1722                    reserved: 0x00,
1723                },
1724                scsi::FeatureDataProfileList {
1725                    profile_number: scsi::PROFILE_CD_ROM.into(),
1726                    current: 0x00,
1727                    reserved: 0x00,
1728                },
1729            ],
1730        };
1731        let mut random_readable = scsi::FeatureDataRandomReadable {
1732            header: scsi::FeatureHeader {
1733                feature_code: (scsi::FeatureNumber::FeatureRandomReadable as u16).into(),
1734                flags: scsi::FeatureHeaderFlags::new()
1735                    .with_current(true)
1736                    .with_persistent(false)
1737                    .with_version(0)
1738                    .with_reserved(0),
1739                additional_length: 0x08,
1740            },
1741            logical_block_size: scsi::ISO_SECTOR_SIZE.into(),
1742            blocking: 16.into(),
1743            error_recovery_page_present: 0x01,
1744            reserved: 0x00,
1745        };
1746
1747        let mut dvd_read = scsi::FeatureDataDvdRead {
1748            header: scsi::FeatureHeader {
1749                feature_code: (scsi::FeatureNumber::FeatureDvdRead as u16).into(),
1750                flags: scsi::FeatureHeaderFlags::new()
1751                    .with_current(true)
1752                    .with_persistent(false)
1753                    .with_version(0x01)
1754                    .with_reserved(0),
1755                additional_length: 0x04,
1756            },
1757            multi_110: 0x00,
1758            reserved: 0x00,
1759            dual_dash_r: 0x00,
1760            reserved2: 0x00,
1761        };
1762
1763        let mut real_time_streaming = scsi::FeatureDataRealTimeStreaming {
1764            header: scsi::FeatureHeader {
1765                feature_code: (FeatureRealTimeStreaming as u16).into(),
1766                flags: scsi::FeatureHeaderFlags::new()
1767                    .with_current(true)
1768                    .with_persistent(true)
1769                    .with_version(0x05)
1770                    .with_reserved(0),
1771                additional_length: 0x04,
1772            },
1773            flags: scsi::RealTimeStreamingFlags::new()
1774                .with_stream_recording(false)
1775                .with_write_speed_in_get_perf(false)
1776                .with_write_speed_in_mp2_a(false)
1777                .with_set_cdspeed(false)
1778                .with_read_buffer_capacity_block(true)
1779                .with_reserved1(0x00),
1780            reserved: [0x00, 0x00, 0x00],
1781        };
1782
1783        *feature_size = 0;
1784        match feature_code {
1785            scsi::FeatureNumber::FeatureProfileList => {
1786                profile_list.profile[0].current =
1787                    self.media_state.lock().drive_state.medium_present() as u8;
1788                *feature_size = size_of::<scsi::GetConfigurationFeatureDataProfileList>();
1789                bytes_used = std::cmp::min(*feature_size, buffer_size);
1790                if bytes_used > 0 {
1791                    external_data
1792                        .writer()
1793                        .write(&profile_list.as_bytes()[..bytes_used])
1794                        .map_err(ScsiDvdError::MemoryAccess)?;
1795                }
1796            }
1797            scsi::FeatureNumber::FeatureCore => {
1798                *feature_size = size_of::<scsi::FeatureDataCore>();
1799                bytes_used = std::cmp::min(*feature_size, buffer_size);
1800                let data = scsi::FeatureDataCore {
1801                    header: scsi::FeatureHeader {
1802                        feature_code: (scsi::FeatureNumber::FeatureCore as u16).into(),
1803                        flags: scsi::FeatureHeaderFlags::new()
1804                            .with_current(true)
1805                            .with_persistent(true)
1806                            .with_version(0x02)
1807                            .with_reserved(0),
1808                        additional_length: 0x08,
1809                    },
1810                    ..FromZeros::new_zeroed()
1811                };
1812                if bytes_used > 0 {
1813                    external_data
1814                        .writer()
1815                        .write(&data.as_bytes()[..bytes_used])
1816                        .map_err(ScsiDvdError::MemoryAccess)?;
1817                }
1818            }
1819            scsi::FeatureNumber::FeatureMorphing => {
1820                *feature_size = size_of::<scsi::FeatureDataMorphing>();
1821                bytes_used = std::cmp::min(*feature_size, buffer_size);
1822                let data = scsi::FeatureDataMorphing {
1823                    header: scsi::FeatureHeader {
1824                        feature_code: (scsi::FeatureNumber::FeatureMorphing as u16).into(),
1825                        flags: scsi::FeatureHeaderFlags::new()
1826                            .with_current(true)
1827                            .with_persistent(true)
1828                            .with_version(0x02)
1829                            .with_reserved(0),
1830                        additional_length: 0x08,
1831                    },
1832                    flags: scsi::FeatureMorphingFlags::new()
1833                        .with_asynchronous(false)
1834                        .with_ocevent(true)
1835                        .with_reserved1(0x00),
1836                    reserved2: [0x00, 0x00, 0x00],
1837                };
1838                if bytes_used > 0 {
1839                    external_data
1840                        .writer()
1841                        .write(&data.as_bytes()[..bytes_used])
1842                        .map_err(ScsiDvdError::MemoryAccess)?;
1843                }
1844            }
1845            scsi::FeatureNumber::FeatureRemovableMedium => {
1846                *feature_size = size_of::<scsi::FeatureDataRemovableMedium>();
1847                bytes_used = std::cmp::min(*feature_size, buffer_size);
1848                let data = scsi::FeatureDataRemovableMedium {
1849                    header: scsi::FeatureHeader {
1850                        feature_code: (scsi::FeatureNumber::FeatureRemovableMedium as u16).into(),
1851                        flags: scsi::FeatureHeaderFlags::new()
1852                            .with_current(true)
1853                            .with_persistent(true)
1854                            .with_version(0x01)
1855                            .with_reserved(0),
1856                        additional_length: 0x04,
1857                    },
1858                    flags: scsi::RemovableMediumFlags::new()
1859                        .with_lockable(true)
1860                        .with_dbml(false)
1861                        .with_default_to_prevent(false)
1862                        .with_eject(true)
1863                        .with_load(true)
1864                        .with_loading_mechanism(0x01),
1865                    reserved2: [0x00, 0x00, 0x00],
1866                };
1867                if bytes_used > 0 {
1868                    external_data
1869                        .writer()
1870                        .write(&data.as_bytes()[..bytes_used])
1871                        .map_err(ScsiDvdError::MemoryAccess)?;
1872                }
1873            }
1874            scsi::FeatureNumber::FeatureRandomReadable => {
1875                let medium_present = self.media_state.lock().drive_state.medium_present();
1876                if !current_only || medium_present {
1877                    random_readable.header.flags.set_current(medium_present);
1878                    *feature_size = size_of::<scsi::FeatureDataRandomReadable>();
1879                    bytes_used = std::cmp::min(*feature_size, buffer_size);
1880                    if bytes_used > 0 {
1881                        external_data
1882                            .writer()
1883                            .write(&random_readable.as_bytes()[..bytes_used])
1884                            .map_err(ScsiDvdError::MemoryAccess)?;
1885                    }
1886                }
1887            }
1888            scsi::FeatureNumber::FeatureCdRead => {
1889                if !current_only {
1890                    *feature_size = size_of::<scsi::FeatureDataCdRead>();
1891                    bytes_used = std::cmp::min(*feature_size, buffer_size);
1892                    let data = scsi::FeatureDataCdRead {
1893                        header: scsi::FeatureHeader {
1894                            feature_code: (scsi::FeatureNumber::FeatureCdRead as u16).into(),
1895                            flags: scsi::FeatureHeaderFlags::new()
1896                                .with_current(false)
1897                                .with_persistent(false)
1898                                .with_version(0x02)
1899                                .with_reserved(0),
1900                            additional_length: 0x04,
1901                        },
1902                        flags: scsi::CDReadFlags::new()
1903                            .with_cd_text(false)
1904                            .with_c2_error_data(false)
1905                            .with_reserved(0x00)
1906                            .with_digital_audio_play(false),
1907                        reserved2: [0x00, 0x00, 0x00],
1908                    };
1909                    if bytes_used > 0 {
1910                        external_data
1911                            .writer()
1912                            .write(&data.as_bytes()[..bytes_used])
1913                            .map_err(ScsiDvdError::MemoryAccess)?;
1914                    }
1915                }
1916            }
1917            scsi::FeatureNumber::FeatureDvdRead => {
1918                let medium_present = self.media_state.lock().drive_state.medium_present();
1919                if !current_only || medium_present {
1920                    *feature_size = size_of::<scsi::FeatureDataDvdRead>();
1921                    bytes_used = std::cmp::min(*feature_size, buffer_size);
1922                    dvd_read.header.flags.set_current(medium_present);
1923                    if bytes_used > 0 {
1924                        external_data
1925                            .writer()
1926                            .write(&dvd_read.as_bytes()[..bytes_used])
1927                            .map_err(ScsiDvdError::MemoryAccess)?;
1928                    }
1929                }
1930            }
1931            FeaturePowerManagement => {
1932                *feature_size = size_of::<scsi::FeatureDataPowerManagement>();
1933                bytes_used = std::cmp::min(*feature_size, buffer_size);
1934                let data = scsi::FeatureDataPowerManagement {
1935                    header: scsi::FeatureHeader {
1936                        feature_code: (FeaturePowerManagement as u16).into(),
1937                        flags: scsi::FeatureHeaderFlags::new()
1938                            .with_current(true)
1939                            .with_persistent(true)
1940                            .with_version(0x00)
1941                            .with_reserved(0),
1942                        additional_length: 0x00,
1943                    },
1944                };
1945                if bytes_used > 0 {
1946                    external_data
1947                        .writer()
1948                        .write(&data.as_bytes()[..bytes_used])
1949                        .map_err(ScsiDvdError::MemoryAccess)?;
1950                }
1951            }
1952            FeatureTimeout => {
1953                *feature_size = size_of::<scsi::FeatureDataTimeout>();
1954                bytes_used = std::cmp::min(*feature_size, buffer_size);
1955                let data = scsi::FeatureDataTimeout {
1956                    header: scsi::FeatureHeader {
1957                        feature_code: (FeatureTimeout as u16).into(),
1958                        flags: scsi::FeatureHeaderFlags::new()
1959                            .with_current(true)
1960                            .with_persistent(true)
1961                            .with_version(0x01)
1962                            .with_reserved(0),
1963                        additional_length: 0x04,
1964                    },
1965                    group: 0x01,
1966                    reserved: 0x00,
1967                    unit_length: 512.into(),
1968                };
1969                if bytes_used > 0 {
1970                    external_data
1971                        .writer()
1972                        .write(&data.as_bytes()[..bytes_used])
1973                        .map_err(ScsiDvdError::MemoryAccess)?;
1974                }
1975            }
1976            FeatureRealTimeStreaming => {
1977                let medium_present = self.media_state.lock().drive_state.medium_present();
1978                if !current_only || medium_present {
1979                    *feature_size = size_of::<scsi::FeatureDataRealTimeStreaming>();
1980                    bytes_used = std::cmp::min(*feature_size, buffer_size);
1981                    real_time_streaming.header.flags.set_current(medium_present);
1982                    if bytes_used > 0 {
1983                        external_data
1984                            .writer()
1985                            .write(&real_time_streaming.as_bytes()[..bytes_used])
1986                            .map_err(ScsiDvdError::MemoryAccess)?;
1987                    }
1988                }
1989            }
1990            _ => {}
1991        }
1992        Ok(bytes_used)
1993    }
1994
1995    fn iso_init_configuration_header(
1996        &self,
1997        external_data: &RequestBuffers<'_>,
1998        data_length: usize,
1999    ) -> Result<usize, ScsiDvdError> {
2000        let current_profile = if self.media_state.lock().drive_state.medium_present() {
2001            scsi::PROFILE_DVD_ROM
2002        } else {
2003            0
2004        };
2005
2006        // data_length field indicates the amount of data available given a sufficient allocation length
2007        // following this field. Thus the size of itself should be excluded.
2008        let header = scsi::GetConfigurationHeader {
2009            data_length: (data_length as u32).into(),
2010            current_profile: current_profile.into(),
2011            ..FromZeros::new_zeroed()
2012        };
2013
2014        let tx = external_data.len();
2015
2016        external_data
2017            .writer()
2018            .write(&header.as_bytes()[..tx])
2019            .map_err(ScsiDvdError::MemoryAccess)?;
2020
2021        Ok(tx)
2022    }
2023
2024    fn handle_no_vpd_page_iso(
2025        &self,
2026        external_data: &RequestBuffers<'_>,
2027        allocation_length: usize,
2028    ) -> Result<usize, ScsiDvdError> {
2029        // return standard inquiry data
2030        let data = scsi::InquiryData {
2031            header: scsi::InquiryDataHeader {
2032                device_type: 0x05,
2033                flags2: scsi::InquiryDataFlag2::new().with_removable_media(true),
2034                versions: 0x00,
2035                flags3: scsi::InquiryDataFlag3::new()
2036                    .with_response_data_format(scsi::T10_RESPONSE_DATA_SPC3),
2037                additional_length: (scsi::INQUIRY_DATA_BUFFER_SIZE
2038                    - (size_of::<scsi::InquiryDataHeader>() as u8)),
2039            },
2040            vendor_id: *b"Msft    ",
2041            product_id: *b"Virtual DVD-ROM ",
2042            product_revision_level: *b"1.0 ",
2043            ..FromZeros::new_zeroed()
2044        };
2045
2046        let tx = std::cmp::min(allocation_length, size_of::<scsi::InquiryData>());
2047        external_data
2048            .writer()
2049            .write(&data.as_bytes()[..tx])
2050            .map_err(ScsiDvdError::MemoryAccess)?;
2051
2052        Ok(tx)
2053    }
2054
2055    fn handle_vpd_supported_pages_iso(
2056        &self,
2057        external_data: &RequestBuffers<'_>,
2058        allocation_length: usize,
2059    ) -> Result<usize, ScsiDvdError> {
2060        let page = [scsi::VPD_SUPPORTED_PAGES, scsi::VPD_DEVICE_IDENTIFIERS];
2061
2062        write_vpd_page(
2063            external_data,
2064            allocation_length,
2065            scsi::VPD_SUPPORTED_PAGES,
2066            &page,
2067        )
2068    }
2069
2070    fn handle_vpd_device_identifiers_iso(
2071        &self,
2072        external_data: &RequestBuffers<'_>,
2073        allocation_length: usize,
2074    ) -> Result<usize, ScsiDvdError> {
2075        // This is the ID used by VHDMP on Windows.
2076        let context_guid = [
2077            0x73, 0x05, 0xe3, 0x43, 0x77, 0x03, 0x54, 0x46, 0x94, 0x95, 0x7d, 0x7c, 0xed, 0x62,
2078            0x4a, 0x7d,
2079        ];
2080        let page = scsi::IsoVpdIdentifiers {
2081            id_page: scsi::VpdIdentificationDescriptor {
2082                code_set: scsi::VPD_CODE_SET_BINARY,
2083                identifiertype: scsi::VPD_IDENTIFIER_TYPE_VENDOR_ID,
2084                reserved3: 0x00,
2085                identifier_length: (size_of::<scsi::IsoVpdIdentifiers>()
2086                    - size_of::<scsi::VpdIdentificationDescriptor>())
2087                    as u8,
2088            },
2089            vendor_id: *b"Msft    ",
2090            context_guid,
2091        };
2092
2093        write_vpd_page(
2094            external_data,
2095            allocation_length,
2096            scsi::VPD_DEVICE_IDENTIFIERS,
2097            &page,
2098        )
2099    }
2100
2101    fn validate_data_cdb_iso(
2102        &self,
2103        external_data: &RequestBuffers<'_>,
2104        request: &Request,
2105        sector_count: u64,
2106        op: ScsiOp,
2107    ) -> Result<RequestParametersIso, ScsiDvdError> {
2108        let (len, offset) = match op {
2109            ScsiOp::READ => {
2110                let cdb = scsi::Cdb10::read_from_prefix(&request.cdb[..]).unwrap().0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
2111                (
2112                    cdb.transfer_blocks.get() as u64,
2113                    cdb.logical_block.get() as u64,
2114                )
2115            }
2116            ScsiOp::READ12 => {
2117                let cdb = scsi::Cdb12::read_from_prefix(&request.cdb[..]).unwrap().0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
2118                (
2119                    cdb.transfer_blocks.get() as u64,
2120                    cdb.logical_block.get() as u64,
2121                )
2122            }
2123            ScsiOp::READ16 => {
2124                let cdb = scsi::Cdb16::read_from_prefix(&request.cdb[..]).unwrap().0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
2125                (cdb.transfer_blocks.get() as u64, cdb.logical_block.get())
2126            }
2127            _ => unreachable!(),
2128        };
2129
2130        if len == 0 {
2131            return Ok(RequestParametersIso {
2132                tx: len as usize,
2133                offset: 0,
2134            });
2135        }
2136
2137        self.validate_media_for_read()?;
2138
2139        let sector_shift = self.sector_shift();
2140        let max = external_data.len() >> sector_shift;
2141        if len as usize > max {
2142            tracelimit::error_ratelimited!(len, max, "illegal block");
2143            return Err(ScsiDvdError::IllegalRequest(
2144                AdditionalSenseCode::ILLEGAL_BLOCK,
2145                0,
2146            ));
2147        }
2148
2149        if sector_count <= offset || sector_count - offset < len {
2150            tracelimit::error_ratelimited!(sector_count, offset, len, "illegal block");
2151            return Err(ScsiDvdError::IllegalRequest(
2152                AdditionalSenseCode::ILLEGAL_BLOCK,
2153                0,
2154            ));
2155        }
2156
2157        let tx = (len as usize) << sector_shift;
2158        Ok(RequestParametersIso {
2159            tx,
2160            offset: (offset * self.balancer()),
2161        })
2162    }
2163
2164    fn iso_get_mode_page(
2165        &self,
2166        external_data: &RequestBuffers<'_>,
2167        buffer_size: usize,
2168        page_code: u8,
2169        mode_page_size: &mut u16,
2170    ) -> Result<usize, ScsiDvdError> {
2171        let tx: usize;
2172        let page_size: usize;
2173        match page_code {
2174            scsi::MODE_PAGE_ERROR_RECOVERY => {
2175                page_size = size_of::<scsi::ModeReadWriteRecoveryPage>();
2176                tx = std::cmp::min(page_size, buffer_size);
2177                let data = scsi::ModeReadWriteRecoveryPage {
2178                    page_code: 0x01,
2179                    page_length: 0x0a,
2180                    bit_info: 0u8,
2181                    read_retry_count: 0u8,
2182                    reserved: [0u8, 0u8, 0u8, 0u8],
2183                    write_retry_count: 0u8,
2184                    reserved2: [0u8, 0u8, 0u8],
2185                };
2186                external_data
2187                    .writer()
2188                    .write(&data.as_bytes()[..tx])
2189                    .map_err(ScsiDvdError::MemoryAccess)?;
2190                *mode_page_size = page_size as u16;
2191            }
2192            scsi::MODE_PAGE_POWER_CONDITION => {
2193                page_size = size_of::<scsi::PowerConditionPage>();
2194                tx = std::cmp::min(page_size, buffer_size);
2195                let data = scsi::PowerConditionPage {
2196                    page_code: 0x1a,
2197                    page_length: 0x0a,
2198                    ..FromZeros::new_zeroed()
2199                };
2200                external_data
2201                    .writer()
2202                    .write(&data.as_bytes()[..tx])
2203                    .map_err(ScsiDvdError::MemoryAccess)?;
2204                *mode_page_size = page_size as u16;
2205            }
2206            scsi::MODE_PAGE_CDVD_INACTIVITY => {
2207                page_size = size_of::<scsi::ModeSenseModePageTimeoutProtect>();
2208                tx = std::cmp::min(page_size, buffer_size);
2209                let data = scsi::ModeSenseModePageTimeoutProtect {
2210                    page_code: 0x1d,
2211                    page_length: 0x0a,
2212                    reserved: [0u8, 0u8],
2213                    bit_info: 0x00,
2214                    reserved2: 0x00,
2215                    group_one_minimum_timeout: [0x00, 0x05],
2216                    group_two_minimum_timeout: [0x00, 0x14],
2217                    group_three_timeout: [0x00, 0x0a],
2218                };
2219                external_data
2220                    .writer()
2221                    .write(&data.as_bytes()[..tx])
2222                    .map_err(ScsiDvdError::MemoryAccess)?;
2223                *mode_page_size = page_size as u16;
2224            }
2225            _ => {
2226                *mode_page_size = 0;
2227                tx = 0;
2228            }
2229        };
2230        Ok(tx)
2231    }
2232
2233    fn iso_init_mode_sense_header(
2234        &self,
2235        external_data: &RequestBuffers<'_>,
2236        buffer_size: usize,
2237        data_length: u16,
2238    ) -> Result<usize, ScsiDvdError> {
2239        let data = scsi::ModeParameterHeader10 {
2240            mode_data_length: (data_length - (size_of::<u16>() as u16)).into(),
2241            block_descriptor_length: 0.into(),
2242            ..FromZeros::new_zeroed()
2243        };
2244        let tx = std::cmp::min(super::MODE_PARAMETER_HEADER10_SIZE, buffer_size);
2245        external_data
2246            .writer()
2247            .write(&data.as_bytes()[..tx])
2248            .map_err(ScsiDvdError::MemoryAccess)?;
2249        Ok(tx)
2250    }
2251
2252    fn validate_media_for_read(&self) -> Result<(), ScsiDvdError> {
2253        let drive_state = self.media_state.lock().drive_state;
2254
2255        match drive_state {
2256            DriveState::MediumPresentTrayOpen => Ok(()),
2257            DriveState::MediumPresentTrayClosed => Ok(()),
2258            DriveState::MediumNotPresentTrayOpen => Err(ScsiDvdError::SenseNotReady(
2259                AdditionalSenseCode::NO_MEDIA_IN_DEVICE,
2260                scsi::MEDIUM_NOT_PRESENT_TRAY_OPEN,
2261            )),
2262            DriveState::MediumNotPresentTrayClosed => Err(ScsiDvdError::SenseNotReady(
2263                AdditionalSenseCode::NO_MEDIA_IN_DEVICE,
2264                scsi::MEDIUM_NOT_PRESENT_TRAY_CLOSED,
2265            )),
2266        }
2267    }
2268
2269    fn iso_init_performance_header(
2270        &self,
2271        external_data: &RequestBuffers<'_>,
2272        buffer_size: u64,
2273        data_length: u64,
2274        except: u8,
2275    ) -> Result<usize, ScsiDvdError> {
2276        let header = scsi::GetPerformanceHeader {
2277            total_data_length: (data_length as u32).into(),
2278            except: except & 0x01,
2279            ..FromZeros::new_zeroed()
2280        };
2281
2282        let tx =
2283            std::cmp::min(size_of::<scsi::GetPerformanceHeader>() as u64, buffer_size) as usize;
2284        external_data
2285            .writer()
2286            .write(&header.as_bytes()[..tx])
2287            .map_err(ScsiDvdError::MemoryAccess)?;
2288        Ok(tx)
2289    }
2290
2291    fn process_result(&self, result: Result<usize, ScsiDvdError>, op: ScsiOp) -> ScsiResult {
2292        let result = match result {
2293            Ok(tx) => ScsiResult {
2294                scsi_status: ScsiStatus::GOOD,
2295                srb_status: SrbStatus::SUCCESS,
2296                tx,
2297                sense_data: None,
2298            },
2299            Err(err) => match err {
2300                ScsiDvdError::IllegalRequest(sense_code, sense_qualifier) => ScsiResult {
2301                    scsi_status: ScsiStatus::CHECK_CONDITION,
2302                    srb_status: SrbStatus::ERROR,
2303                    tx: 0,
2304                    sense_data: Some(illegal_request_sense_iso(sense_code, sense_qualifier)),
2305                },
2306                ScsiDvdError::DataOverrun => ScsiResult {
2307                    scsi_status: ScsiStatus::CHECK_CONDITION,
2308                    srb_status: SrbStatus::DATA_OVERRUN,
2309                    tx: 0,
2310                    sense_data: None,
2311                },
2312                ScsiDvdError::MemoryAccess(_) => ScsiResult {
2313                    scsi_status: ScsiStatus::CHECK_CONDITION,
2314                    srb_status: SrbStatus::INVALID_REQUEST,
2315                    tx: 0,
2316                    sense_data: Some(illegal_request_sense_iso(
2317                        AdditionalSenseCode::INVALID_CDB,
2318                        0,
2319                    )),
2320                },
2321                ScsiDvdError::SenseNotReady(sense_code, sense_qualifier) => ScsiResult {
2322                    scsi_status: ScsiStatus::CHECK_CONDITION,
2323                    srb_status: SrbStatus::ERROR,
2324                    tx: 0,
2325                    sense_data: Some(SenseData::new(
2326                        SenseKey::NOT_READY,
2327                        sense_code,
2328                        sense_qualifier,
2329                    )),
2330                },
2331                ScsiDvdError::IoError(err) => match err {
2332                    DiskError::UnsupportedEject => ScsiResult {
2333                        scsi_status: ScsiStatus::CHECK_CONDITION,
2334                        srb_status: SrbStatus::INVALID_REQUEST,
2335                        tx: 0,
2336                        sense_data: Some(illegal_request_sense_iso(
2337                            AdditionalSenseCode::ILLEGAL_COMMAND,
2338                            0,
2339                        )),
2340                    },
2341                    DiskError::ReservationConflict => ScsiResult {
2342                        scsi_status: ScsiStatus::RESERVATION_CONFLICT,
2343                        srb_status: SrbStatus::ERROR,
2344                        tx: 0,
2345                        sense_data: Some(SenseData::new(
2346                            SenseKey::ILLEGAL_REQUEST,
2347                            AdditionalSenseCode::COMMAND_SEQUENCE_ERROR,
2348                            scsi::SCSI_SENSEQ_CAPACITY_DATA_CHANGED,
2349                        )),
2350                    },
2351                    _ => ScsiResult {
2352                        scsi_status: ScsiStatus::CHECK_CONDITION,
2353                        srb_status: SrbStatus::ERROR,
2354                        tx: 0,
2355                        sense_data: None,
2356                    },
2357                },
2358                ScsiDvdError::IllegalRequestNoSenseData => ScsiResult {
2359                    scsi_status: ScsiStatus::CHECK_CONDITION,
2360                    srb_status: SrbStatus::INVALID_REQUEST,
2361                    tx: 0,
2362                    sense_data: None,
2363                },
2364            },
2365        };
2366
2367        self.sense_data.set(result.sense_data.as_ref());
2368        tracing::trace!(scsi_result = ?result, ?op, "process_result completed.");
2369
2370        result
2371    }
2372}
2373
2374fn illegal_request_sense_iso(sense_code: AdditionalSenseCode, sense_qualifier: u8) -> SenseData {
2375    match sense_code {
2376        AdditionalSenseCode::ILLEGAL_COMMAND
2377        | AdditionalSenseCode::INVALID_CDB
2378        | AdditionalSenseCode::NO_SENSE
2379        | AdditionalSenseCode::INVALID_FIELD_PARAMETER_LIST
2380        | AdditionalSenseCode::PARAMETER_LIST_LENGTH
2381        | AdditionalSenseCode::ILLEGAL_BLOCK
2382        | AdditionalSenseCode::INVALID_MEDIA
2383        | AdditionalSenseCode::SAVING_PARAMETER_NOT_SUPPORTED
2384        | AdditionalSenseCode::MEDIUM_REMOVAL_PREVENTED => {
2385            SenseData::new(SenseKey::ILLEGAL_REQUEST, sense_code, sense_qualifier)
2386        }
2387        _ => unreachable!(),
2388    }
2389}
2390
2391#[cfg(test)]
2392mod tests {
2393    use super::Media;
2394    use crate::SavedSenseData;
2395    use crate::ScsiSaveRestore;
2396    use crate::ScsiSavedState;
2397    use crate::scsi;
2398    use crate::scsidvd::ISO_SECTOR_SIZE;
2399    use crate::scsidvd::SimpleScsiDvd;
2400    use disk_backend::Disk;
2401    use disk_backend::DiskError;
2402    use disk_backend::DiskIo;
2403    use guestmem::GuestMemory;
2404    use guestmem::MemoryWrite;
2405    use inspect::Inspect;
2406    use pal_async::async_test;
2407    use scsi::AdditionalSenseCode;
2408    use scsi::ScsiOp;
2409    use scsi::SenseKey;
2410    use scsi_buffers::OwnedRequestBuffers;
2411    use scsi_buffers::RequestBuffers;
2412    use scsi_core::AsyncScsiDisk;
2413    use scsi_core::Request;
2414    use scsi_core::save_restore::IsoMediumEvent;
2415    use scsi_core::save_restore::ScsiDvdSavedState;
2416    use test_with_tracing::test;
2417    use zerocopy::IntoBytes;
2418
2419    #[derive(Debug)]
2420    struct TestDisk {
2421        sector_count: u64,
2422        sector_size: u32,
2423        storage: Vec<u8>,
2424        read_only: bool,
2425    }
2426
2427    impl Inspect for TestDisk {
2428        fn inspect(&self, req: inspect::Request<'_>) {
2429            req.respond();
2430        }
2431    }
2432
2433    impl TestDisk {
2434        pub fn new(sector_size: u32, sector_count: u64, read_only: bool) -> TestDisk {
2435            let buffer = make_repeat_data_buffer(sector_count as usize, sector_size as usize);
2436
2437            TestDisk {
2438                sector_count,
2439                sector_size,
2440                storage: buffer,
2441                read_only,
2442            }
2443        }
2444    }
2445
2446    impl DiskIo for TestDisk {
2447        fn disk_type(&self) -> &str {
2448            "test"
2449        }
2450
2451        fn sector_count(&self) -> u64 {
2452            self.sector_count
2453        }
2454
2455        fn sector_size(&self) -> u32 {
2456            self.sector_size
2457        }
2458
2459        fn is_read_only(&self) -> bool {
2460            self.read_only
2461        }
2462
2463        fn disk_id(&self) -> Option<[u8; 16]> {
2464            None
2465        }
2466
2467        fn physical_sector_size(&self) -> u32 {
2468            self.sector_size
2469        }
2470
2471        fn is_fua_respected(&self) -> bool {
2472            false
2473        }
2474
2475        async fn eject(&self) -> Result<(), DiskError> {
2476            Err(DiskError::UnsupportedEject)
2477        }
2478
2479        async fn read_vectored(
2480            &self,
2481            buffers: &RequestBuffers<'_>,
2482            sector: u64,
2483        ) -> Result<(), DiskError> {
2484            let offset = sector as usize * self.sector_size() as usize;
2485            let end_point = offset + buffers.len();
2486
2487            if self.storage.len() < end_point {
2488                return Err(DiskError::IllegalBlock);
2489            }
2490
2491            buffers.writer().write(&self.storage[offset..end_point])?;
2492            Ok(())
2493        }
2494
2495        async fn write_vectored(
2496            &self,
2497            _buffers: &RequestBuffers<'_>,
2498            _sector: u64,
2499            _fua: bool,
2500        ) -> Result<(), DiskError> {
2501            todo!()
2502        }
2503
2504        async fn sync_cache(&self) -> Result<(), DiskError> {
2505            todo!()
2506        }
2507
2508        async fn unmap(
2509            &self,
2510            _sector: u64,
2511            _count: u64,
2512            _block_level_only: bool,
2513        ) -> Result<(), DiskError> {
2514            Ok(())
2515        }
2516
2517        fn unmap_behavior(&self) -> disk_backend::UnmapBehavior {
2518            disk_backend::UnmapBehavior::Ignored
2519        }
2520    }
2521
2522    fn new_scsi_dvd(sector_size: u32, sector_count: u64, read_only: bool) -> SimpleScsiDvd {
2523        let disk = TestDisk::new(sector_size, sector_count, read_only);
2524        let scsi_dvd = SimpleScsiDvd::new(Some(Disk::new(disk).unwrap()));
2525        let sector_shift = ISO_SECTOR_SIZE.trailing_zeros() as u8;
2526        assert_eq!(scsi_dvd.sector_count(), sector_count / scsi_dvd.balancer());
2527        assert_eq!(scsi_dvd.sector_shift(), sector_shift);
2528        if let Media::Loaded(disk) = &*scsi_dvd.media.read() {
2529            assert_eq!(disk.is_read_only(), read_only);
2530            assert_eq!(disk.sector_size(), sector_size);
2531        } else {
2532            panic!("unexpected Media::Unloaded");
2533        }
2534        scsi_dvd
2535    }
2536
2537    fn make_repeat_data_buffer(sector_count: usize, sector_size: usize) -> Vec<u8> {
2538        let mut buf = vec![0u8; sector_count * sector_size];
2539        let mut temp = vec![0u8; sector_size];
2540        assert!(sector_size > 2);
2541        temp[sector_size / 2 - 1] = 2;
2542        temp[sector_size / 2] = 3;
2543
2544        for i in (0..buf.len()).step_by(temp.len()) {
2545            let end_point = i + temp.len();
2546            buf[i..end_point].copy_from_slice(&temp);
2547        }
2548
2549        buf
2550    }
2551
2552    async fn check_execute_scsi(
2553        scsi_dvd: &mut SimpleScsiDvd,
2554        external_data: &RequestBuffers<'_>,
2555        request: &Request,
2556        pass: bool,
2557    ) {
2558        let result = scsi_dvd.execute_scsi(external_data, request).await;
2559        match pass {
2560            true if result.scsi_status != scsi::ScsiStatus::GOOD => {
2561                panic!(
2562                    "execute_scsi failed! request: {:?} result: {:?}",
2563                    request, result
2564                );
2565            }
2566            false if result.scsi_status == scsi::ScsiStatus::GOOD => {
2567                panic!(
2568                    "execute_scsi passed! request: {:?} result: {:?}",
2569                    request, result
2570                );
2571            }
2572            _ => (),
2573        }
2574    }
2575
2576    fn make_cdb16_request(operation_code: ScsiOp, start_lba: u64, lba_count: u32) -> Request {
2577        let cdb = scsi::Cdb16 {
2578            operation_code,
2579            flags: scsi::Cdb16Flags::new(),
2580            logical_block: start_lba.into(),
2581            transfer_blocks: lba_count.into(),
2582            reserved2: 0,
2583            control: 0,
2584        };
2585        let mut data = [0u8; 16];
2586        data[..].copy_from_slice(cdb.as_bytes());
2587        Request {
2588            cdb: data,
2589            srb_flags: 0,
2590        }
2591    }
2592
2593    fn check_guest_memory(
2594        guest_mem: &GuestMemory,
2595        start_lba: u64,
2596        buff: &[u8],
2597        sector_size: usize,
2598    ) -> bool {
2599        let mut b = vec![0u8; buff.len()];
2600        if guest_mem.read_at(start_lba, &mut b).is_err() {
2601            panic!("guest_mem read error");
2602        };
2603        buff[..].eq(&b[..]) && (b[sector_size / 2 - 1] == 2) && (b[sector_size / 2] == 3)
2604    }
2605
2606    fn save_scsi_dvd(scsi_dvd: &SimpleScsiDvd) -> ScsiDvdSavedState {
2607        let saved_state =
2608            if let Some(ScsiSavedState::ScsiDvd(saved_state)) = scsi_dvd.save().unwrap() {
2609                saved_state
2610            } else {
2611                panic!("saved_state cannot be none")
2612            };
2613        let media_state = scsi_dvd.media_state.lock();
2614        assert_eq!(saved_state.persistent, media_state.persistent);
2615        assert_eq!(saved_state.prevent, media_state.prevent);
2616        assert_eq!(saved_state.drive_state, media_state.drive_state);
2617        assert_eq!(
2618            saved_state.pending_medium_event,
2619            media_state.pending_medium_event
2620        );
2621        let sense = scsi_dvd.sense_data.get();
2622        let sense_data = sense.map(|sense| SavedSenseData {
2623            sense_key: sense.header.sense_key.0,
2624            additional_sense_code: sense.additional_sense_code.0,
2625            additional_sense_code_qualifier: sense.additional_sense_code_qualifier,
2626        });
2627        assert_eq!(saved_state.sense_data, sense_data);
2628        saved_state
2629    }
2630
2631    fn restore_scsi_dvd(saved_state: ScsiDvdSavedState, scsi_dvd: &SimpleScsiDvd) {
2632        if scsi_dvd
2633            .restore(&ScsiSavedState::ScsiDvd(saved_state))
2634            .is_err()
2635        {
2636            panic!("restore scsi dvd failed. saved_state {:?}", saved_state);
2637        }
2638
2639        let media_state = scsi_dvd.media_state.lock();
2640        assert_eq!(saved_state.persistent, media_state.persistent);
2641        assert_eq!(saved_state.prevent, media_state.prevent);
2642        assert_eq!(saved_state.drive_state, media_state.drive_state);
2643        assert_eq!(
2644            saved_state.pending_medium_event,
2645            media_state.pending_medium_event
2646        );
2647        let sense = scsi_dvd.sense_data.get();
2648        let sense_data = sense.map(|sense| SavedSenseData {
2649            sense_key: sense.header.sense_key.0,
2650            additional_sense_code: sense.additional_sense_code.0,
2651            additional_sense_code_qualifier: sense.additional_sense_code_qualifier,
2652        });
2653        assert_eq!(saved_state.sense_data, sense_data);
2654    }
2655
2656    #[test]
2657    fn validate_new_scsi_dvd() {
2658        new_scsi_dvd(512, 2048, true);
2659    }
2660
2661    fn make_medium_removal_request(prevent: bool, persistent: bool) -> Request {
2662        let cdb = scsi::CdbMediaRemoval {
2663            operation_code: ScsiOp::MEDIUM_REMOVAL,
2664            lun: 0,
2665            reserved: [0; 2],
2666            flags: scsi::MediaRemovalFlags::new()
2667                .with_prevent(prevent)
2668                .with_persistent(persistent),
2669        };
2670        let mut data = [0u8; 16];
2671        data[..size_of::<scsi::CdbMediaRemoval>()].copy_from_slice(cdb.as_bytes());
2672        Request {
2673            cdb: data,
2674            srb_flags: 0,
2675        }
2676    }
2677
2678    fn make_eject_request() -> Request {
2679        let cdb = scsi::StartStop {
2680            operation_code: ScsiOp::START_STOP_UNIT,
2681            immediate: 0,
2682            reserved2: [0; 2],
2683            flag: scsi::StartStopFlags::new()
2684                .with_start(false)
2685                .with_load_eject(true),
2686            control: 0,
2687        };
2688        let mut data = [0u8; 16];
2689        data[..size_of::<scsi::StartStop>()].copy_from_slice(cdb.as_bytes());
2690        Request {
2691            cdb: data,
2692            srb_flags: 0,
2693        }
2694    }
2695
2696    /// A tray lock belongs to the initiator that set it, not to the drive.
2697    ///
2698    /// PREVENT ALLOW MEDIUM REMOVAL is scoped to the I_T nexus, so when the nexus
2699    /// goes away - the guest closed the channel, or was reset in place, which
2700    /// closes it too - the lock has to go with it. Otherwise the next guest to
2701    /// boot cannot eject, and its START STOP UNIT is failed with
2702    /// MEDIUM_REMOVAL_PREVENTED by a decision it never made (umbrella #186).
2703    ///
2704    /// The first eject is the control: without it a pass would only show that
2705    /// ejecting works, not that clearing the nexus is what made it work.
2706    #[async_test]
2707    async fn validate_tray_lock_does_not_outlive_the_nexus() {
2708        let mut scsi_dvd = new_scsi_dvd(ISO_SECTOR_SIZE, 2048, true);
2709        let external_data = OwnedRequestBuffers::new(&[0]);
2710        let guest_mem = GuestMemory::allocate(ISO_SECTOR_SIZE as usize);
2711
2712        check_execute_scsi(
2713            &mut scsi_dvd,
2714            &external_data.buffer(&guest_mem),
2715            &make_medium_removal_request(true, true),
2716            true,
2717        )
2718        .await;
2719        assert!(scsi_dvd.media_state.lock().prevent);
2720        // Set PERSISTENT as well, so that the assertion after the clear can fail.
2721        assert!(scsi_dvd.media_state.lock().persistent);
2722
2723        // Locked, so this one must be refused.
2724        check_execute_scsi(
2725            &mut scsi_dvd,
2726            &external_data.buffer(&guest_mem),
2727            &make_eject_request(),
2728            false,
2729        )
2730        .await;
2731
2732        scsi_dvd.clear_nexus_state();
2733        assert!(!scsi_dvd.media_state.lock().prevent);
2734        assert!(!scsi_dvd.media_state.lock().persistent);
2735
2736        // Same command, same drive, same media: it succeeds now only because the
2737        // lock did not survive the nexus.
2738        check_execute_scsi(
2739            &mut scsi_dvd,
2740            &external_data.buffer(&guest_mem),
2741            &make_eject_request(),
2742            true,
2743        )
2744        .await;
2745    }
2746
2747    /// Sense data and a queued medium-change event are per-nexus too, and the
2748    /// tray state is NOT.
2749    ///
2750    /// The second half is the guard against over-clearing: an ejected tray must
2751    /// stay ejected, as it would on real hardware, so a fix that resets the whole
2752    /// MediaState would fail here.
2753    ///
2754    /// Order matters. The eject has to come FIRST and the failing command last:
2755    /// a command that succeeds clears the sense slot on its way out, so an eject
2756    /// placed after it would leave the slot empty and the sense assertion would
2757    /// hold whether or not anything cleared it. Written the other way round this
2758    /// test passed against a `clear_nexus_state` mutated to do nothing at all.
2759    #[async_test]
2760    async fn validate_sense_clears_but_the_tray_state_survives() {
2761        let mut scsi_dvd = new_scsi_dvd(ISO_SECTOR_SIZE, 2048, true);
2762        let external_data = OwnedRequestBuffers::new(&[0]);
2763        let guest_mem = GuestMemory::allocate(ISO_SECTOR_SIZE as usize);
2764
2765        check_execute_scsi(
2766            &mut scsi_dvd,
2767            &external_data.buffer(&guest_mem),
2768            &make_eject_request(),
2769            true,
2770        )
2771        .await;
2772        let ejected = scsi_dvd.media_state.lock().drive_state;
2773        // The eject queued a medium-change event, which belongs to this initiator.
2774        assert_eq!(
2775            scsi_dvd.media_state.lock().pending_medium_event,
2776            IsoMediumEvent::MediaToNoMedia
2777        );
2778
2779        // A failed command leaves sense data behind for the initiator to collect.
2780        check_execute_scsi(
2781            &mut scsi_dvd,
2782            &external_data.buffer(&guest_mem),
2783            &make_cdb16_request(ScsiOp::READ16, 4096, 1),
2784            false,
2785        )
2786        .await;
2787        assert!(scsi_dvd.sense_data.get().is_some());
2788
2789        scsi_dvd.clear_nexus_state();
2790
2791        assert!(scsi_dvd.sense_data.get().is_none());
2792        assert_eq!(
2793            scsi_dvd.media_state.lock().pending_medium_event,
2794            IsoMediumEvent::None
2795        );
2796        assert_eq!(
2797            scsi_dvd.media_state.lock().drive_state,
2798            ejected,
2799            "the tray belongs to the drive, not to the initiator"
2800        );
2801    }
2802
2803    #[async_test]
2804    async fn validate_read16() {
2805        let sector_size = 512;
2806        let sector_count = 2048;
2807        let mut scsi_dvd = new_scsi_dvd(sector_size, sector_count, true);
2808
2809        let dvd_sector_size = ISO_SECTOR_SIZE as u64;
2810        let dvd_sector_count = scsi_dvd.sector_count();
2811        let external_data =
2812            OwnedRequestBuffers::linear(0, (dvd_sector_size * dvd_sector_count) as usize, true);
2813        let guest_mem = GuestMemory::allocate(4096);
2814        let start_lba = 0;
2815        let lba_count = 2;
2816        let request = make_cdb16_request(ScsiOp::READ16, start_lba, lba_count);
2817
2818        println!("read disk to guest_mem2 ...");
2819        check_execute_scsi(
2820            &mut scsi_dvd,
2821            &external_data.buffer(&guest_mem),
2822            &request,
2823            true,
2824        )
2825        .await;
2826
2827        println!("validate guest_mem2 ...");
2828        let data = make_repeat_data_buffer(sector_count as usize, sector_size as usize);
2829        assert_eq!(
2830            check_guest_memory(
2831                &guest_mem,
2832                0,
2833                &data[..(ISO_SECTOR_SIZE * lba_count) as usize],
2834                sector_size as usize
2835            ),
2836            true
2837        );
2838    }
2839
2840    #[test]
2841    fn validate_save_restore_scsi_dvd_no_change() {
2842        let scsi_dvd = new_scsi_dvd(512, 2048, true);
2843        let saved_state = save_scsi_dvd(&scsi_dvd);
2844        restore_scsi_dvd(saved_state, &scsi_dvd);
2845    }
2846
2847    #[test]
2848    fn validate_save_restore_scsi_dvd_with_sense_data() {
2849        let scsi_dvd = new_scsi_dvd(512, 2048, true);
2850        let mut saved_state = save_scsi_dvd(&scsi_dvd);
2851        saved_state.sense_data = Some(SavedSenseData {
2852            sense_key: SenseKey::UNIT_ATTENTION.0,
2853            additional_sense_code: AdditionalSenseCode::OPERATING_CONDITIONS_CHANGED.0,
2854            additional_sense_code_qualifier: scsi::SCSI_SENSEQ_OPERATING_DEFINITION_CHANGED,
2855        });
2856        restore_scsi_dvd(saved_state, &scsi_dvd);
2857    }
2858}