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