How to issue a READ CD command to a CD-ROM drive in Windows?

非 Y 不嫁゛ 提交于 2020-01-02 07:29:22

问题


I'm working on an application that needs to issue raw SCSI commands to a CD-ROM drive. Currently, I'm struggling with sending a READ CD (0xBE) command to the drive and getting back the data from a given sector of the CD.

Consider the following code :

#include <windows.h>
#include <winioctl.h>
#include <ntddcdrm.h>
#include <ntddscsi.h>
#include <stddef.h>

int main(void)
{
  HANDLE fh;
  DWORD ioctl_bytes;
  BOOL ioctl_rv;
  const UCHAR cdb[] = { 0xBE, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0 };
  UCHAR buf[2352];
  struct sptd_with_sense
  {
    SCSI_PASS_THROUGH_DIRECT s;
    UCHAR sense[128];
  } sptd;

  fh = CreateFile("\\\\.\\E:", GENERIC_READ | GENERIC_WRITE,
    FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL, NULL);

  memset(&sptd, 0, sizeof(sptd));
  sptd.s.Length = sizeof(sptd.s);
  sptd.s.CdbLength = sizeof(cdb);
  sptd.s.DataIn = SCSI_IOCTL_DATA_IN;
  sptd.s.TimeOutValue = 30;
  sptd.s.DataBuffer = buf;
  sptd.s.DataTransferLength = sizeof(buf);
  sptd.s.SenseInfoLength = sizeof(sptd.sense);
  sptd.s.SenseInfoOffset = offsetof(struct sptd_with_sense, sense);
  memcpy(sptd.s.Cdb, cdb, sizeof(cdb));

  ioctl_rv = DeviceIoControl(fh, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd,
    sizeof(sptd), &sptd, sizeof(sptd), &ioctl_bytes, NULL);

  CloseHandle(fh);

  return 0;
}

The CDB was assembled according to MMC-6 Revision 2g, and should transfer 1 sector from LBA 1. Since I'm working with CD-DA discs only, each sector is 2352 bytes, which explains why sizeof(buf) is 2352.

Error-checking was omitted for brevity. The debugger shows that the DeviceIoControl call returns successfully and ioctl_bytes is 0x2c, while the values inside sptd.s are as follows :

Length              0x002c      unsigned short
ScsiStatus          0x00        unsigned char
PathId              0x00        unsigned char
TargetId            0x00        unsigned char
Lun                 0x00        unsigned char
CdbLength           0x0c        unsigned char
SenseInfoLength     0x00        unsigned char
DataIn              0x01        unsigned char
DataTransferLength  0x00000930  unsigned long
TimeOutValue        0x0000001e  unsigned long
DataBuffer          0x0012f5f8  void *
SenseInfoOffset     0x0000002c  unsigned long

This shows that the command has been executed successfully by the drive, as ScsiStatus is 0 (SCSI_STATUS_GOOD), and no sense data was returned. However, the buffer for the data is not written to, since the debugger shows that it is filled with 0xcc, as the application is compiled in debug mode.

However, when I change the CDB to the standard INQUIRY command like this :

const UCHAR cdb[] = { 0x12, 0, 0, 0, 36, 0 };

The buffer is properly filled with inquiry data, and I am able to read the name of the drive, vendor, and everything else.

I've already tried aligning the target buffer, according to Microsoft's documentation for SCSI_PASS_THROUGH_DIRECT, which says that The DataBuffer member of SCSI_PASS_THROUGH_DIRECT is a pointer to this adapter device aligned buffer. Experimentally aligning the buffer to 64 bytes did not work, and issuing a IOCTL_SCSI_GET_CAPABILITIES, which is supposed to return the required alignment, gave me the following information :

Length                      0x00000018  unsigned long
MaximumTransferLength       0x00020000  unsigned long
MaximumPhysicalPages        0x00000020  unsigned long
SupportedAsynchronousEvents 0x00000000  unsigned long
AlignmentMask               0x00000001  unsigned long
TaggedQueuing               0x00        unsigned char
AdapterScansDown            0x00        unsigned char
AdapterUsesPio              0x01        unsigned char

Which leads me to believe that alignment is not required since AlignmentMask is 1, and thus it does not seem like this is the cause of the problem. Interestingly, AdapterUsesPio is 1, although the Device Manager says otherwise.

For the record, the code below works properly on Linux, and the target buffer is filled with data from the CD. Same as on Windows, the returned SCSI status is 0, and no sense data is returned.

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <scsi/sg.h>
#include <scsi/scsi.h>
#include <linux/cdrom.h>
#include <sys/ioctl.h>

int main(void)
{
  int fd = open("/dev/sr0", O_RDONLY | O_NONBLOCK);
  if(fd == -1) { perror("open"); return 1; }

  {
    struct sg_io_hdr sgio;
    unsigned char cdb[] = { 0xBE, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0 };
    unsigned char buf[2352];
    unsigned char sense[128];
    int rv;

    sgio.interface_id = 'S';
    sgio.dxfer_direction = SG_DXFER_FROM_DEV;
    sgio.cmd_len = sizeof(cdb);
    sgio.cmdp = cdb;
    sgio.dxferp = buf;
    sgio.dxfer_len = sizeof(buf);
    sgio.sbp = sense;
    sgio.mx_sb_len = sizeof(sense);
    sgio.timeout = 30000;

    rv = ioctl(fd, SG_IO, &sgio);
    if(rv == -1) { perror("ioctl"); return 1; }
  }
  close(fd);
  return 0;
}

The Windows code is compiled with Visual Studio C++ 2010 Express and WinDDK 7600.16385.1, on Windows XP. It is run on Windows XP as well.


回答1:


The issue lies within an improperly formed CDB, although valid in terms of syntax. What I failed to see in the MMC specification was this :

The 9th byte is supposed to contain bits used for selecting the kind of data the drive is supposed to return. In the code in the question, I set it to 0, which means that I requested "No fields" from the drive. Changing this byte to 0x10 (User Data) results in both the Linux and Windows versions returning the same data for a given sector. I still don't know why Linux returned some data in the buffer even with the original form of the CDB.

The proper CDB for the READ CD command, when reading one CD-DA sector at LBA 1, should therefore look like this :

const unsigned char cdb[] = { 0xBE, 0, 0, 0, 0, 1, 0, 0, 1, 0x10, 0, 0 };



回答2:


Your code BTW is always going to fail in Windows 7 due to read security restrictions. You can send most SCSI commands with the DeviceIOControl API, but when it comes to data or raw reads, you must use the prescribed SPTI method to read a sector or Windows 7 will block it, with or without administrator privileges, so FYI, you can no longer do this the SCSI way if you want more compatibility!

Here's what the SPTI prescribed way would look like, and thankfully it's much less code than building up a SCSI command packet using OxBE or READ10 (which is what you should've used if you just wanted the data of a data sector as it's a SCSI-1 command, and not 0xBE which is less compatible):

RAW_READ_INFO rawRead;

if ( ghCDRom ) {
    rawRead.TrackMode = CDDA;
    rawRead.SectorCount = nSectors;
// Must use standard CDROM data sector size of 2048, and not 2352 as one would expect
// while buffer must be able to hold the raw size, 2352 * nSectors, as you *would* expect!
    rawRead.DiskOffset.QuadPart = LBA * CDROM_SECTOR_SIZE;
// Call DeviceIoControl, and trap both possible errors: a return value of FALSE
// and the number of bytes returned not matching expectations!
    return (
        DeviceIoControl(ghCDRom, IOCTL_CDROM_RAW_READ, &rawRead, sizeof(RAW_READ_INFO), gAlignedSCSIBuffer, SCSI_BUFFER_SIZE, (PDWORD)&gnNumberOfBytes, NULL)
        &&
        gnNumberOfBytes == (nSectors * RAW_SECTOR_SIZE)
    );

So in short, google around the IOCTL_CDROM_RAW_READ command. The above code snippet would work for an audio sector and return 2352 bytes. This can work all the way back to Windows NT4.0 if your CreateFile() call is proper. But yes, if you use IOCTL_SCSI_PASS_THROUGH_DIRECT and try building your own 0xBE SCSI command packet, Windows 7 WILL block it! Microsoft wants you to use the IOCTL_CDROM_RAW_READ for raw reads. You can build up other SCSI command packets to read the TOC, obtain drive capabilities, but a read command will get blocked and DeviceIoControl will raise an "Invalid function" error. Apparently, at least for Windows 10, my software worked again and the restriction was removed, but since Windows 7 has a large user-install base, you will want to do it the SPTI prescribed way ANYWAY, plus IOCTL_CDROM_RAW_READ knows a few more less common read commands than the universal 0xBE one for old oddball drives, so it's better to use it anyway!



来源:https://stackoverflow.com/questions/29884540/how-to-issue-a-read-cd-command-to-a-cd-rom-drive-in-windows

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!