Index: /trunk/src/VBox/Devices/Storage/ATAPIPassthrough.cpp
===================================================================
--- /trunk/src/VBox/Devices/Storage/ATAPIPassthrough.cpp	(revision 65963)
+++ /trunk/src/VBox/Devices/Storage/ATAPIPassthrough.cpp	(revision 65964)
@@ -19,4 +19,5 @@
 #include <iprt/assert.h>
 #include <iprt/mem.h>
+#include <iprt/string.h>
 
 #include <VBox/log.h>
@@ -628,2 +629,327 @@
 }
 
+
+static uint8_t atapiPassthroughCmdErrorSimple(uint8_t *pbSense, size_t cbSense, uint8_t uATAPISenseKey, uint8_t uATAPIASC)
+{
+    memset(pbSense, '\0', cbSense);
+    if (RT_LIKELY(cbSense >= 13))
+    {
+        pbSense[0] = 0x70 | (1 << 7);
+        pbSense[2] = uATAPISenseKey & 0x0f;
+        pbSense[7] = 10;
+        pbSense[12] = uATAPIASC;
+    }
+    return SCSI_STATUS_CHECK_CONDITION;
+}
+
+
+DECLHIDDEN(bool) ATAPIPassthroughParseCdb(const uint8_t *pbCdb, size_t cbCdb, size_t cbBuf,
+                                          PTRACKLIST pTrackList, uint8_t *pbSense, size_t cbSense,
+                                          PDMMEDIATXDIR *penmTxDir, size_t *pcbXfer,
+                                          size_t *pcbSector, uint8_t *pu8ScsiSts)
+{
+    uint32_t uLba = 0;
+    uint32_t cSectors = 0;
+    size_t cbSector = 0;
+    size_t cbXfer = 0;
+    bool fPassthrough = false;
+    PDMMEDIATXDIR enmTxDir = PDMMEDIATXDIR_NONE;
+
+    RT_NOREF(cbCdb);
+
+    switch (pbCdb[0])
+    {
+        /* First the commands we can pass through without further processing. */
+        case SCSI_BLANK:
+        case SCSI_CLOSE_TRACK_SESSION:
+        case SCSI_LOAD_UNLOAD_MEDIUM:
+        case SCSI_PAUSE_RESUME:
+        case SCSI_PLAY_AUDIO_10:
+        case SCSI_PLAY_AUDIO_12:
+        case SCSI_PLAY_AUDIO_MSF:
+        case SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL:
+        case SCSI_REPAIR_TRACK:
+        case SCSI_RESERVE_TRACK:
+        case SCSI_SCAN:
+        case SCSI_SEEK_10:
+        case SCSI_SET_CD_SPEED:
+        case SCSI_SET_READ_AHEAD:
+        case SCSI_START_STOP_UNIT:
+        case SCSI_STOP_PLAY_SCAN:
+        case SCSI_SYNCHRONIZE_CACHE:
+        case SCSI_TEST_UNIT_READY:
+        case SCSI_VERIFY_10:
+            fPassthrough = true;
+            break;
+        case SCSI_ERASE_10:
+            uLba = scsiBE2H_U32(pbCdb + 2);
+            cbXfer = scsiBE2H_U16(pbCdb + 7);
+            enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_FORMAT_UNIT:
+            cbXfer = cbBuf;
+            enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_GET_CONFIGURATION:
+            cbXfer = scsiBE2H_U16(pbCdb + 7);
+            enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_GET_EVENT_STATUS_NOTIFICATION:
+            cbXfer = scsiBE2H_U16(pbCdb + 7);
+            enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_GET_PERFORMANCE:
+            cbXfer = cbBuf;
+            enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_INQUIRY:
+            cbXfer = scsiBE2H_U16(pbCdb + 3);
+            enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_MECHANISM_STATUS:
+            cbXfer = scsiBE2H_U16(pbCdb + 8);
+            enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_MODE_SELECT_10:
+            cbXfer = scsiBE2H_U16(pbCdb + 7);
+            enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_MODE_SENSE_10:
+            cbXfer = scsiBE2H_U16(pbCdb + 7);
+            enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_READ_10:
+            uLba = scsiBE2H_U32(pbCdb + 2);
+            cSectors = scsiBE2H_U16(pbCdb + 7);
+            cbSector = 2048;
+            cbXfer = cSectors * cbSector;
+            enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_READ_12:
+            uLba = scsiBE2H_U32(pbCdb + 2);
+            cSectors = scsiBE2H_U32(pbCdb + 6);
+            cbSector = 2048;
+            cbXfer = cSectors * cbSector;
+            enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_READ_BUFFER:
+            cbXfer = scsiBE2H_U24(pbCdb + 6);
+            enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_READ_BUFFER_CAPACITY:
+            cbXfer = scsiBE2H_U16(pbCdb + 7);
+            enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_READ_CAPACITY:
+            cbXfer = 8;
+            enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_READ_CD:
+        case SCSI_READ_CD_MSF:
+        {
+            /* Get sector size based on the expected sector type field. */
+            switch ((pbCdb[1] >> 2) & 0x7)
+            {
+                case 0x0: /* All types. */
+                {
+                    uint32_t iLbaStart;
+
+                    if (pbCdb[0] == SCSI_READ_CD)
+                        iLbaStart = scsiBE2H_U32(&pbCdb[2]);
+                    else
+                        iLbaStart = scsiMSF2LBA(&pbCdb[3]);
+
+                    if (pTrackList)
+                        cbSector = ATAPIPassthroughTrackListGetSectorSizeFromLba(pTrackList, iLbaStart);
+                    else
+                        cbSector = 2048; /* Might be incorrect if we couldn't determine the type. */
+                    break;
+                }
+                case 0x1: /* CD-DA */
+                    cbSector = 2352;
+                    break;
+                case 0x2: /* Mode 1 */
+                    cbSector = 2048;
+                    break;
+                case 0x3: /* Mode 2 formless */
+                    cbSector = 2336;
+                    break;
+                case 0x4: /* Mode 2 form 1 */
+                    cbSector = 2048;
+                    break;
+                case 0x5: /* Mode 2 form 2 */
+                    cbSector = 2324;
+                    break;
+                default: /* Reserved */
+                    AssertMsgFailed(("Unknown sector type\n"));
+                    cbSector = 0; /** @todo we should probably fail the command here already. */
+            }
+
+            if (pbCdb[0] == SCSI_READ_CD)
+                cbXfer = scsiBE2H_U24(pbCdb + 6) * cbSector;
+            else /* SCSI_READ_MSF */
+            {
+                cSectors = scsiMSF2LBA(pbCdb + 6) - scsiMSF2LBA(pbCdb + 3);
+                if (cSectors > 32)
+                    cSectors = 32; /* Limit transfer size to 64~74K. Safety first. In any case this can only harm software doing CDDA extraction. */
+                cbXfer = cSectors * cbSector;
+            }
+            enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+            fPassthrough = true;
+            break;
+        }
+        case SCSI_READ_DISC_INFORMATION:
+            cbXfer = scsiBE2H_U16(pbCdb + 7);
+            enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_READ_DVD_STRUCTURE:
+            cbXfer = scsiBE2H_U16(pbCdb + 8);
+            enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_READ_FORMAT_CAPACITIES:
+            cbXfer = scsiBE2H_U16(pbCdb + 7);
+            enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_READ_SUBCHANNEL:
+            cbXfer = scsiBE2H_U16(pbCdb + 7);
+            enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_READ_TOC_PMA_ATIP:
+            cbXfer = scsiBE2H_U16(pbCdb + 7);
+            enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_READ_TRACK_INFORMATION:
+            cbXfer = scsiBE2H_U16(pbCdb + 7);
+            enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_REPORT_KEY:
+            cbXfer = scsiBE2H_U16(pbCdb + 8);
+            enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_REQUEST_SENSE:
+            cbXfer = pbCdb[4];
+            enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_SEND_CUE_SHEET:
+            cbXfer = scsiBE2H_U24(pbCdb + 6);
+            enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_SEND_DVD_STRUCTURE:
+            cbXfer = scsiBE2H_U16(pbCdb + 8);
+            enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_SEND_EVENT:
+            cbXfer = scsiBE2H_U16(pbCdb + 8);
+            enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_SEND_KEY:
+            cbXfer = scsiBE2H_U16(pbCdb + 8);
+            enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_SEND_OPC_INFORMATION:
+            cbXfer = scsiBE2H_U16(pbCdb + 7);
+            enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_SET_STREAMING:
+            cbXfer = scsiBE2H_U16(pbCdb + 9);
+            enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_WRITE_10:
+        case SCSI_WRITE_AND_VERIFY_10:
+            uLba = scsiBE2H_U32(pbCdb + 2);
+            cSectors = scsiBE2H_U16(pbCdb + 7);
+            if (pTrackList)
+                cbSector = ATAPIPassthroughTrackListGetSectorSizeFromLba(pTrackList, uLba);
+            else
+                cbSector = 2048;
+            cbXfer = cSectors * cbSector;
+            enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_WRITE_12:
+            uLba = scsiBE2H_U32(pbCdb + 2);
+            cSectors = scsiBE2H_U32(pbCdb + 6);
+            if (pTrackList)
+                cbSector = ATAPIPassthroughTrackListGetSectorSizeFromLba(pTrackList, uLba);
+            else
+                cbSector = 2048;
+            cbXfer = cSectors * cbSector;
+            enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_WRITE_BUFFER:
+            switch (pbCdb[1] & 0x1f)
+            {
+                case 0x04: /* download microcode */
+                case 0x05: /* download microcode and save */
+                case 0x06: /* download microcode with offsets */
+                case 0x07: /* download microcode with offsets and save */
+                case 0x0e: /* download microcode with offsets and defer activation */
+                case 0x0f: /* activate deferred microcode */
+                    LogRel(("ATAPI: CD-ROM passthrough command attempted to update firmware, blocked\n"));
+                    *pu8ScsiSts = atapiPassthroughCmdErrorSimple(pbSense, cbSense, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+                    break;
+                default:
+                    cbXfer = scsiBE2H_U16(pbCdb + 6);
+                    enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
+                    fPassthrough = true;
+                    break;
+            }
+            break;
+        case SCSI_REPORT_LUNS: /* Not part of MMC-3, but used by Windows. */
+            cbXfer = scsiBE2H_U32(pbCdb + 6);
+            enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
+            fPassthrough = true;
+            break;
+        case SCSI_REZERO_UNIT:
+            /* Obsolete command used by cdrecord. What else would one expect?
+             * This command is not sent to the drive, it is handled internally,
+             * as the Linux kernel doesn't like it (message "scsi: unknown
+             * opcode 0x01" in syslog) and replies with a sense code of 0,
+             * which sends cdrecord to an endless loop. */
+            *pu8ScsiSts = atapiPassthroughCmdErrorSimple(pbSense, cbSense, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
+            break;
+        default:
+            LogRel(("ATAPI: Passthrough unimplemented for command %#x\n", pbCdb[0]));
+            *pu8ScsiSts = atapiPassthroughCmdErrorSimple(pbSense, cbSense, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
+            break;
+    }
+
+    if (fPassthrough)
+    {
+        *penmTxDir = enmTxDir;
+        *pcbXfer   = cbXfer;
+        *pcbSector = cbSector;
+    }
+
+    return fPassthrough;
+}
+
Index: /trunk/src/VBox/Devices/Storage/ATAPIPassthrough.h
===================================================================
--- /trunk/src/VBox/Devices/Storage/ATAPIPassthrough.h	(revision 65963)
+++ /trunk/src/VBox/Devices/Storage/ATAPIPassthrough.h	(revision 65964)
@@ -19,4 +19,6 @@
 
 #include <VBox/cdefs.h>
+#include <VBox/vmm/pdmifs.h>
+#include <VBox/vmm/pdmstorageifs.h>
 
 RT_C_DECLS_BEGIN
@@ -70,4 +72,24 @@
 DECLHIDDEN(uint32_t) ATAPIPassthroughTrackListGetSectorSizeFromLba(PTRACKLIST pTrackList, uint32_t iAtapiLba);
 
+/**
+ * Parses the given CDB and returns whether it is safe to pass it through to the host drive.
+ *
+ * @returns Flag whether passing the CDB through to the host drive is safe.
+ * @param   pbCdb         The CDB to parse.
+ * @param   cbCdb         Size of the CDB in bytes.
+ * @param   cbBuf         Size of the guest buffer.
+ * @param   pTrackList    The track list for the current medium if available (optional).
+ * @param   pbSense       Pointer to the sense buffer.
+ * @param   cbSense       Size of the sense buffer.
+ * @param   penmTxDir     Where to store the transfer direction (guest to host or vice versa).
+ * @param   pcbXfer       Where to store the transfer size encoded in the CDB.
+ * @param   pcbSector     Where to store the sector size used for the transfer.
+ * @param   pu8ScsiSts    Where to store the SCSI status code.
+ */
+DECLHIDDEN(bool) ATAPIPassthroughParseCdb(const uint8_t *pbCdb, size_t cbCdb, size_t cbBuf,
+                                          PTRACKLIST pTrackList, uint8_t *pbSense, size_t cbSense,
+                                          PDMMEDIATXDIR *penmTxDir, size_t *pcbXfer,
+                                          size_t *pcbSector, uint8_t *pu8ScsiSts);
+
 RT_C_DECLS_END
 
