/*++

Module Name:

    devctl.c

Abstract:

    A Win32 application for generating I/O requests to test the functioning of a driver based
    software write blocker.
 

Author:

    Dan Allen - June, 2004
    National Institute of Standards and Technology

Environment:

    User mode.

Description:

    The Software Write Blocker test suite uses two kernel mode filter drivers to bracket
    the write blocker driver(s) under test.  An upper filter driver (Pitcher.sys) is used
    to build and throw IRP requests at the write blocker. A lower filter driver (Catcher.sys)
    is used to catch and complete all IRP's thrown by the upper filter.

    The upper filter driver implements a custom device IOCTL for communicating with
    user mode appications (IOCTL_IRP_PASSTHRU). The input argument for this IOCTL is
    a structure containing the basic data for a kernel mode IRP. When the upper filter
    receives an IOCTL_IRP_PASSTHRU request, it builds an IRP from the data in the input buffer
    and passes it to the next lower driver on the associated device stack. When the constructed IRP
    completes, the I/O status block for the constructed IRP is returned in the output buffer of the
    original IOCTL request.

    The lower filter driver catches and completes any test IRP's passed by the write blocker.
    When a test IRP is caught and completed by the lower filter a custom status is returned
    in the IOSB. This status indicates the IRP was completed by Catcher.sys rather than being
    blocked by the SWB driver.

    This application uses the IOCRL_IRP_PASSTHRU mechanism to generate a series of test IRP's
    for the write blocker under test and produce a report detailing which IRP's were blocked and
    which IRP's were not.
 

Revision History:

--*/

#include <ntddk.h>			// Get DDK definitions
#include "..\..\Include\swbts.h"	// Include custom IOCTL definitions
#undef NT_INCLUDED			// Pretend ntddk.h never happened
#pragma warning(push) 			// Save compiler warning settings
#pragma warning (disable: 4005) 	// Disable macro redefinition warnings
#undef InterlockedCompareExchange	// Remove brain dead macro definitions
#undef InterlockedCompareExchange64	// of these function names set by ntddk.h
#undef _SLIST_HEADER_			// Suppress SLIST definitions in winbase.h
#include <xwindows.h> 			// Use our expurgated version of windows.h
#include <winioctl.h>			// Use stock definitions
#pragma warning (pop)			// Restore compiler settings
#include <stdio.h>			// Include standard user mode stuff
#include <stdlib.h>
#include <scsi.h>
#include <setupapi.h>
#include <time.h>

#define NAME_COUNT  25

#define BOOLEAN_TO_STRING(_b_) \
( (_b_) ? "True" : "False" )

void PrintError(ULONG);

#if defined(_X86_)
    #define PAGE_SIZE  0x1000
    #define PAGE_SHIFT 12L
#elif defined(_AMD64_)
    #define PAGE_SIZE  0x1000
    #define PAGE_SHIFT 12L
#elif defined(_IA64_)
    #define PAGE_SIZE 0x2000
    #define PAGE_SHIFT 13L
#else
    // undefined platform?
    #define PAGE_SIZE  0x1000
    #define PAGE_SHIFT 12L
#endif

// Function prototypes

void  Do_IRP_MJ_SCSI();
PCHAR IrpMajorFunctionString (UCHAR MajorFunction);
PCHAR ScsiopToString(UCHAR Scsiop); 
PCHAR NtStatusToString( DWORD NtStatus);

// Global variables and structures

DWORD		dwIoctlBase = CTL_CODE(FILE_DEVICE_DISK, 0, 0, 3);
DWORD		dwIrpCnt, dwCdbCnt, dwIrpsBlocked, dwCdbsBlocked;
ULONG		irpCode, length = 0, errorCode = 0, returned = 0, sectorSize = 512;
USER_IRP	Irp;
HANDLE	        fileHandle = NULL;
char		*pszTestType, *pszTestPattern; 

// This array and the following enum are order dependent on each other

char		*pszCategory[] = {"READ", "WRITE", "OTHER", "VENDOR_SPECIFIC", "UNDEFINED"};
typedef enum	{eRead,  eWrite,  eOther, eVspec, eUndef} CATEGORY;

// This array defines the test category of all valid IRP major function codes

CATEGORY        eTestType, IRP_CATEGORIES[] =
{
	eWrite,		// IRP_MJ_CREATE
	eOther,		// IRP_MJ_CREATE_NAMED_PIPE
	eOther,		// IRP_MJ_CLOSE
	eRead,		// IRP_MJ_READ
	eWrite,		// IRP_MJ_WRITE
	eOther,		// IRP_MJ_QUERY_INFORMATION
	eWrite,		// IRP_MJ_SET_INFORMATION
	eRead,		// IRP_MJ_QUERY_EA
	eWrite,		// IRP_MJ_SET_EA
	eWrite,		// IRP_MJ_FLUSH_BUFFERS
	eRead,		// IRP_MJ_QUERY_VOLUME_INFORMATION
	eWrite,		// IRP_MJ_SET_VOLUME_INFORMATION
	eOther,		// IRP_MJ_DIRECTORY_CONTROL
	eOther,		// IRP_MJ_FILE_SYSTEM_CONTROL
	eOther,		// IRP_MJ_DEVICE_CONTROL
	eOther,		// IRP_MJ_INTERNAL_DEVICE_CONTROL (aka IRP_MJ_SCSI)
	eOther,		// IRP_MJ_SHUTDOWN
	eOther,		// IRP_MJ_LOCK_CONTROL
	eOther,		// IRP_MJ_CLEANUP
	eOther,		// IRP_MJ_CREATE_MAILSLOT
	eOther,		// IRP_MJ_QUERY_SECURITY
	eWrite,		// IRP_MJ_SET_SECURITY
	eOther,		// IRP_MJ_POWER
	eOther,		// IRP_MJ_SYSTEM_CONTROL
	eOther,		// IRP_MJ_DEVICE_CHANGE
	eRead,		// IRP_MJ_QUERY_QUOTA
	eWrite,		// IRP_MJ_SET_QUOTA
	eOther		// IRP_MJ_PNP
};

// This array defines the test category of all possible SCSI CDB's

CATEGORY	CDB_CATEGORIES[] =
{
   eOther,   eOther,   eVspec,   eOther,   eWrite,   eOther,   eVspec,  eWrite,
//   00        01        02        03        04        05        06       07
   eRead,    eVspec,   eWrite,   eOther,   eVspec,   eVspec,   eVspec,  eVspec,
//   08        09        0A        0B        0C        0D        0E        0F
   eWrite,   eVspec,   eOther,   eOther,   eVspec,   eOther,   eOther,  eOther,
//   10        11        12        13        14        15        16       17
   eWrite,   eWrite,   eOther,   eOther,   eOther,   eOther,   eOther,  eUndef,
//   18        19        1A        1B        1C        1D        1E       1F
   eVspec,   eVspec,   eVspec,   eVspec,   eOther,   eOther,   eVspec,  eVspec,
//   20        21        22        23        24        25        26       27
   eRead,    eVspec,   eWrite,   eOther,   eWrite,   eVspec,   eWrite,  eOther,
//   28        29        2A        2B        2C        2D        2E       2F
   eRead,    eRead,    eRead,    eOther,   eOther,   eWrite,   eOther,  eRead,
//   30        31        32        33        34        35        36       37
   eOther,   eOther,   eWrite,   eWrite,   eRead,    eUndef,   eRead,   eWrite, 
//   38        39        3A        3B        3C        3D        3E       3F
   eOther,   eWrite,   eRead,    eRead,    eRead,    eRead,    eOther,  eRead, 
//   40        41        42        43        44        45        46       47
   eRead,    eRead,    eOther,   eOther,   eOther,   eOther,   eOther,  eUndef, 
//   48        49        4A        4B        4C        4D        4E       4F
   eWrite,   eWrite,   eRead,    eWrite,   eOther,   eOther,   eOther,  eOther, 
//   50        51        52        53        54        55        56       57
   eWrite,   eUndef,   eOther,   eWrite,   eOther,   eWrite,   eOther,  eOther, 
//   58        59        5A        5B        5C        5D        5E       5F
   eUndef,   eUndef,   eUndef,   eUndef,   eUndef,   eUndef,   eUndef,  eUndef, 
//   60        61        62        63        64        65        66       67
   eUndef,   eUndef,   eUndef,   eUndef,   eUndef,   eUndef,   eUndef,  eUndef, 
//   68        69        6A        6B        6C        6D        6E       6F
   eUndef,   eUndef,   eUndef,   eUndef,   eUndef,   eUndef,   eUndef,  eUndef, 
//   70        71        72        73        74        75        76       77
   eUndef,   eUndef,   eUndef,   eUndef,   eUndef,   eUndef,   eUndef,  eWrite, 
//   78        79        7A        7B        7C        7D        7E       7F
   eWrite,   eWrite,   eWrite,   eWrite,   eRead,    eWrite,   eOther,  eOther, 
//   80        81        82        83        84        85        86       87
   eRead,    eUndef,   eWrite,   eUndef,   eOther,   eOther,   eWrite,  eOther,
//   88        89        8A        8B        8C        8D        8E       8F
   eOther,   eWrite,   eOther,   eWrite,   eUndef,   eUndef,   eUndef,  eUndef, 
//   90        91        92        93        94        95        96       97
   eUndef,   eUndef,   eUndef,   eUndef,   eUndef,   eUndef,   eUndef,  eUndef, 
//   98        99        9A        9B        9C        9D        9E       9F
   eOther,   eWrite,   eUndef,   eOther,   eOther,   eOther,    eOther,  eOther, 
//   A0        A1        A2        A3        A4        A5        A6       A7
   eRead,    eUndef,   eWrite,   eUndef,   eWrite,   eOther,   eWrite,  eOther, 
//   A8        A9        AA        AB        AC        AD        AE       AF
   eRead,    eRead,    eRead,    eOther,   eOther,   eUndef,   eOther,  eRead, 
//   B0        B1        B2        B3        B4        B5        B6       B7
   eOther,    eRead,   eRead,   eOther,    eRead,   eOther,   eRead,   eOther, 
//   B8        B9        BA        BB        BC        BD        BE       BF
   eVspec,   eVspec,   eVspec,   eVspec,   eVspec,   eVspec,   eVspec,  eVspec, 
//   C0        C1        C2        C3        C4        C5        C6       C7
   eVspec,   eVspec,   eVspec,   eVspec,   eVspec,   eVspec,   eVspec,  eVspec, 
//   C8        C9        CA        CB        CC        CD        CE       CF
   eVspec,   eVspec,   eVspec,   eVspec,   eVspec,   eVspec,   eVspec,  eVspec, 
//   D0        D1        D2        D3        D4        D5        D6       D7
   eVspec,   eVspec,   eVspec,   eVspec,   eVspec,   eVspec,   eVspec,  eVspec, 
//   D8        D9        DA        DB        DC        DD        DE       DF
   eVspec,   eVspec,   eVspec,   eVspec,   eVspec,   eVspec,   eVspec,  eVspec, 
//   E0        E1        E2        E3        E4        E5        E6       E7
   eVspec,   eVspec,   eVspec,   eVspec,   eVspec,   eVspec,   eVspec,  eVspec, 
//   E8        E9        EA        EB        EC        ED        EE       EF
   eVspec,   eVspec,   eVspec,   eVspec,   eVspec,   eVspec,   eVspec,  eVspec, 
//   F0        F1        F2        F3        F4        F5        F6       F7
   eVspec,   eVspec,   eVspec,   eVspec,   eVspec,   eVspec,   eVspec,  eVspec, 
//   F8        F9        FA        FB        FC        FD        FE       FF
};


/*++

Main entry point

--*/

VOID __cdecl main(int argc, char *argv[])
{
    int		i;
    BOOL	status = 0, bShouldBlock=0;
    DWORD	accessMode = 0, shareMode = 0, dwGuidCnt, dwLen;
    ULONG	alignmentMask = 0; // default == no alignment requirement
    PUCHAR	dataBuffer = NULL;
    UCHAR	string[NAME_COUNT + 1];
    TCHAR	szPathName[64], pFlag;
    HDEVINFO	hDeviceInfo;	
    time_t	ticks;

    // Print test header

    time(&ticks);
    printf("ACES Software Write Blocker Test Suite V1.2\n");
    printf(asctime(localtime(&ticks)));

    /* Set test category */

    pszTestType = argv[2];
    switch (pszTestType[0])
    {
        case 'W':
        case 'w':
            eTestType = eWrite;
            break;

        case 'R':
        case 'r':
            eTestType = eRead;
            break;

        case 'O':
	case 'o':
            eTestType = eOther;
            break;

        case 'V':
	case 'v':
            eTestType = eVspec;
            break;

        case 'U':
	case 'u':
            eTestType = eUndef;
            break;

        default:
            printf("ERROR: Invalid test category '%s'\n", pszTestType);
            return;
    }

    // Print test case parameters

    printf("Test case:          %s\n", argv[1]);
    printf("Command category:   %s\n", pszCategory[eTestType]);
    printf("Number of drives:   %d\n", atoi(argv[3]));
    printf("Protection pattern: %s\n\n", pszTestPattern=argv[4]);


    /* Loop through specified number of devices */

    for(i=0; i < atoi(argv[3]); i++)
    {

       // Build device pathname

       sprintf(szPathName, "\\\\.\\PhysicalDrive%d", i+1);

        // Get protection flag for this device

       pFlag = pszTestPattern[i];
       printf("Testing device %s\n", szPathName);
       printf("Device is software WRITE %s\n", (pFlag == 'P' || pFlag == 'p') ? "PROTECTED" : "ENABLED");

       // Get handle to device

       fileHandle = CreateFile(szPathName,                                   // Device name
                               GENERIC_READ || GENERIC_WRITE,                // Access mode
                               FILE_SHARE_READ || FILE_SHARE_WRITE,	     // Share mode
                               NULL,
                               OPEN_EXISTING,
                               0,
                               NULL);

       if (fileHandle == INVALID_HANDLE_VALUE)
       {
           errorCode = GetLastError();
           printf("Error opening %s,  Error: %d\n",
               argv[1], errorCode);
          PrintError(errorCode);
          return;
       }

       /* Loop through all IRP function codes */

//       printf("\n\n*** Testing IRP interface on %s ***\n\n", argv[1]);
       dwIrpCnt = dwCdbCnt = dwCdbsBlocked = dwIrpsBlocked = 0;
       memset(&Irp, 0, sizeof(Irp));

 //      printf("           Function                     Function    Sub-function           Write Blocker\n");
 //      printf("             Name                         Code          Code               Action Taken\n");
 //      printf("----------------------------------------------------------------------------------------------------\n");

       printf("\n");
       printf("      IRP Function                  Code   Result\n");
       printf("--------------------------------------------------\n");
       for(irpCode=0; irpCode<=IRP_MJ_MAXIMUM_FUNCTION; irpCode++)
       {
//          printf(" %-40.40s(0x%2.2X)  ", IrpMajorFunctionString((UCHAR)irpCode), irpCode);

          // Perform function dependent setup/or subfunction testing

          switch(irpCode)
          {
             case IRP_MJ_SCSI:

               // Test this IRP with all CDB's in the selected test category

	       printf(" %-34.34s(0x%2.2X)\n", IrpMajorFunctionString((UCHAR)irpCode), irpCode);
               Do_IRP_MJ_SCSI();
               continue;

             default:

	       // Test IRP command category

	       if(IRP_CATEGORIES[irpCode] != eTestType)
                  continue;

               // Execute a "skelatal" instance of this IRP

               printf(" %-34.34s(0x%2.2X)  ", IrpMajorFunctionString((UCHAR)irpCode), irpCode);
               Irp.IoStack.MajorFunction = (UCHAR) irpCode;
               status = DeviceIoControl(fileHandle,
                                        IOCTL_IRP_PASSTHRU,
                                        &Irp,
                                        sizeof(Irp),
                                        &Irp,
                                        sizeof(Irp),
		                        &returned,
                                       FALSE);
               if(!status)
               {
                  printf("*** IOCTL Failure ***\n");
                  PrintError(GetLastError());
                  return;
               }
               dwIrpCnt++;
               if(Irp.IoStatus.Status != STATUS_SWBTS_CAUGHT)
               {
                  printf("BLOCKED\n");
                  dwIrpsBlocked++;
               }
               else
               {
                  printf("ALLOWED\n");
               }
           }
       }
       printf("\n");
       printf(" *************** TEST RESULT SUMMARY *************\n\n");
       printf("   Total IRP's issued ..................... %4.4d\n", dwIrpCnt);
       printf("   Total IRP's blocked .................... %4.4d\n", dwIrpsBlocked);
       printf("   Total CDB's issued ..................... %4.4d\n", dwCdbCnt);
       printf("   Total CDB's blocked .................... %4.4d\n", dwCdbsBlocked);
       printf("   Total I/O's issued ..................... %4.4d\n", dwIrpCnt+dwCdbCnt);
       printf("   Total I/O's blocked .................... %4.4d\n", dwIrpsBlocked+dwCdbsBlocked);
    }
    return;
}

      
/*++

Perform IRP_MJ_SCSI tests

++*/

void Do_IRP_MJ_SCSI()
{

  USHORT Scsiop;
  BOOL   status;

  // Setup IRP parameters for SCSI

  Irp.IoStack.MajorFunction = IRP_MJ_SCSI;
  memset(&Irp.Parameters.Scsi.Srb, 0, sizeof(SCSI_REQUEST_BLOCK)); 
  Irp.Parameters.Scsi.Srb.Length = sizeof(SCSI_REQUEST_BLOCK);
  Irp.Parameters.Scsi.Srb.Function = SRB_FUNCTION_EXECUTE_SCSI;
  Irp.Parameters.Scsi.Srb.SenseInfoBufferLength = sizeof(Irp.Parameters.Scsi.SenseInfo);
  Irp.Parameters.Scsi.Srb.SrbFlags = SRB_FLAGS_DATA_OUT;
  Irp.Parameters.Scsi.Srb.DataTransferLength = sizeof(Irp.Parameters.Scsi.Data);

  // Loop through all possible SCSI Commands


  printf("\n  SCSI Operation         Opcode\n");
  printf("  -----------------------------\n");
  for(Scsiop=0; Scsiop<=255; Scsiop++)
  {
     // Skip CDB if not in selected test category

       if(CDB_CATEGORIES[Scsiop] != eTestType)
                  continue;

     // Issue the CDB

     printf("  %-22.22s (0x%2.2X)", ScsiopToString((UCHAR) Scsiop), Scsiop);
     Irp.IoStatus.Status = 0;
     Irp.Parameters.Scsi.Srb.Cdb[0] = (UCHAR) Scsiop;
     status = DeviceIoControl(fileHandle,
                              IOCTL_IRP_PASSTHRU,
                              &Irp,
                              sizeof(Irp),
                              &Irp,
                              sizeof(Irp),
                              &returned,
                              FALSE);

     // Check IOCTL status

     if(!status)
     {
        printf("*** IOCTL Failure ***\n");
        PrintError(GetLastError());
        return;
     }

     // Increment CDB counts

     dwCdbCnt++;
     if(Irp.IoStatus.Status != STATUS_SWBTS_CAUGHT)
     {
         printf("            BLOCKED\n");
         dwCdbsBlocked++;
     }
     else
         printf("            ALLOWED\n");
  }
  printf("\n"); 
}
     
/*++

Get caching mode sense page.

Input:

	dHandle - a handle to the device to be queried

Ouput:

	None

Abstract:

	Issues a SenseMode request to a SCSI device to obtain the Caching
	Mode page and fprints the returned data to the standard out

*/
/*
VOID PrintCachingModePage(HANDLE dHandle)
{
	BOOL status;
	ULONG length = 0;
	ULONG errorCode = 0;
	ULONG returned = 0;

	printf(" *****  Caching Mode Sense Page  *****\n");

    ZeroMemory(&sptwb,sizeof(SCSI_PASS_THROUGH_WITH_BUFFERS));
    sptwb.spt.Length = sizeof(SCSI_PASS_THROUGH);
    sptwb.spt.PathId = 0;
    sptwb.spt.TargetId = 1;
    sptwb.spt.Lun = 0;
    sptwb.spt.CdbLength = CDB6GENERIC_LENGTH;
    sptwb.spt.SenseInfoLength = SPT_SENSE_LENGTH;
    sptwb.spt.DataIn = SCSI_IOCTL_DATA_IN;
    sptwb.spt.DataTransferLength = 192;
    sptwb.spt.TimeOutValue = 2;
    sptwb.spt.DataBufferOffset = offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS,ucDataBuf);
    sptwb.spt.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS,ucSenseBuf);
    sptwb.spt.Cdb[0] = SCSIOP_MODE_SENSE;
    sptwb.spt.Cdb[1] = 0x08; // target shall not return any block descriptors
    sptwb.spt.Cdb[2] = MODE_PAGE_CACHING;
    sptwb.spt.Cdb[4] = 192;
    length = offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS,ucDataBuf) +  sptwb.spt.DataTransferLength;
    status = DeviceIoControl(dHandle,
                             IOCTL_SCSI_PASS_THROUGH,
                             &sptwb,
                             sizeof(SCSI_PASS_THROUGH),
                             &sptwb,
                             length,
                             &returned,
                             FALSE);

    PrintStatusResults(status,returned,&sptwb,length);
}
*/
/*
VOID
PrintDataBuffer(PUCHAR DataBuffer, ULONG BufferLength)
{
    ULONG Cnt, index;
	TCHAR szData[17];;

	printf("                                      Dump of %d byte buffer @%8.8x\n", BufferLength,DataBuffer);
    printf("                                      ----------------------------\n\n");
    for (Cnt = 0, index=0; Cnt < BufferLength; Cnt++)
	{
       if ((Cnt) % 16 == 0)
	   {
          printf(" %03X  ",Cnt);
		  memset(szData, 0, sizeof(szData));
       }
       printf("%02X  ", DataBuffer[Cnt]);
	   szData[Cnt%16] = __toascii(DataBuffer[Cnt]);
       if ((Cnt+1) % 8 == 0)
	   {
          printf(" ");
       }
       if ((Cnt+1) % 16 == 0)
	   {
          printf(" %s\n", szData);

       }
    }
    printf("\n\n");
}

VOID PrintAdapterDescriptor(PSTORAGE_ADAPTER_DESCRIPTOR AdapterDescriptor)
{
    ULONG trueMaximumTransferLength;
    PUCHAR busType;

    if (AdapterDescriptor->BusType <= NUMBER_OF_BUS_TYPE_STRINGS) {
        busType = BusTypeStrings[AdapterDescriptor->BusType];
    } else {
        busType = BusTypeStrings[NUMBER_OF_BUS_TYPE_STRINGS-1];
    }

    // subtract one page, as transfers do not always start on a page boundary
    if (AdapterDescriptor->MaximumPhysicalPages > 1) {
        trueMaximumTransferLength = AdapterDescriptor->MaximumPhysicalPages - 1;
    } else {
        trueMaximumTransferLength = 1;
    }
    // make it into a byte value
    trueMaximumTransferLength <<= PAGE_SHIFT;

    // take the minimum of the two
    if (trueMaximumTransferLength > AdapterDescriptor->MaximumTransferLength) {
        trueMaximumTransferLength = AdapterDescriptor->MaximumTransferLength;
    }

    // always allow at least a single page transfer
    if (trueMaximumTransferLength < PAGE_SIZE) {
        trueMaximumTransferLength = PAGE_SIZE;
    }

    puts("\n *** TARGET STORAGE ADAPTER DESCRIPTOR *****\n\n");
    printf("              Version: %08x\n"
           "            TotalSize: %08x\n"
           "MaximumTransferLength: %08x (bytes)\n"
           " MaximumPhysicalPages: %08x\n"
           "  TrueMaximumTransfer: %08x (bytes)\n"
           "        AlignmentMask: %08x\n"
           "       AdapterUsesPio: %s\n"
           "     AdapterScansDown: %s\n"
           "      CommandQueueing: %s\n"
           "  AcceleratedTransfer: %s\n"
           "             Bus Type: %s\n"
           "    Bus Major Version: %04x\n"
           "    Bus Minor Version: %04x\n",
           AdapterDescriptor->Version,
           AdapterDescriptor->Size,
           AdapterDescriptor->MaximumTransferLength,
           AdapterDescriptor->MaximumPhysicalPages,
           trueMaximumTransferLength,
           AdapterDescriptor->AlignmentMask,
           BOOLEAN_TO_STRING(AdapterDescriptor->AdapterUsesPio),
           BOOLEAN_TO_STRING(AdapterDescriptor->AdapterScansDown),
           BOOLEAN_TO_STRING(AdapterDescriptor->CommandQueueing),
           BOOLEAN_TO_STRING(AdapterDescriptor->AcceleratedTransfer),
           busType,
           AdapterDescriptor->BusMajorVersion,
           AdapterDescriptor->BusMinorVersion);
    printf("\n\n");
}

/*++

PrintDeviceDescriptor

Abstract

	Formats and prints the contents of a device descriptor to the
	standard output
	
Input

	DeviceDescriptor - a pointer to a STORAGE_DEVICE_DESCRIPTOR
	                   containing the data to be printed
					   
Output

	None
	
--*/
/*
VOID PrintDeviceDescriptor(PSTORAGE_DEVICE_DESCRIPTOR DeviceDescriptor)
{
    PUCHAR vendorId = "";
    PUCHAR productId = "";
    PUCHAR productRevision = "";
    PUCHAR serialNumber = "";
    PUCHAR busType;

    if (DeviceDescriptor->BusType <= NUMBER_OF_BUS_TYPE_STRINGS) {
        busType = BusTypeStrings[DeviceDescriptor->BusType];
    } else {
        busType = BusTypeStrings[NUMBER_OF_BUS_TYPE_STRINGS-1];
    }

    if ((DeviceDescriptor->ProductIdOffset != 0) &&
        (DeviceDescriptor->ProductIdOffset != -1)) {
        productId        = (PUCHAR)(DeviceDescriptor);
        productId       += (ULONG_PTR)DeviceDescriptor->ProductIdOffset;
    }
    if ((DeviceDescriptor->VendorIdOffset != 0) &&
        (DeviceDescriptor->VendorIdOffset != -1)) {
        vendorId         = (PUCHAR)(DeviceDescriptor);
        vendorId        += (ULONG_PTR)DeviceDescriptor->VendorIdOffset;
    }
    if ((DeviceDescriptor->ProductRevisionOffset != 0) &&
        (DeviceDescriptor->ProductRevisionOffset != -1)) {
        productRevision  = (PUCHAR)(DeviceDescriptor);
        productRevision += (ULONG_PTR)DeviceDescriptor->ProductRevisionOffset;
    }
    if ((DeviceDescriptor->SerialNumberOffset != 0) &&
        (DeviceDescriptor->SerialNumberOffset != -1)) {
        serialNumber     = (PUCHAR)(DeviceDescriptor);
        serialNumber    += (ULONG_PTR)DeviceDescriptor->SerialNumberOffset;
    }


    puts("\n ***** TARGET STORAGE DEVICE DESCRIPTOR *****\n\n");
    printf("              Version: %08x\n"
           "            TotalSize: %08x\n"
           "           DeviceType: %08x\n"
           "   DeviceTypeModifier: %08x\n"
           "       RemovableMedia: %s\n"
           "      CommandQueueing: %s\n"
           "            Vendor Id: %s\n"
           "           Product Id: %s\n"
           "     Product Revision: %s\n"
           "        Serial Number: %s\n"
           "             Bus Type: %s\n"
           "       Raw Properties: %s\n",
           DeviceDescriptor->Version,
           DeviceDescriptor->Size,
           DeviceDescriptor->DeviceType,
           DeviceDescriptor->DeviceTypeModifier,
           BOOLEAN_TO_STRING(DeviceDescriptor->RemovableMedia),
           BOOLEAN_TO_STRING(DeviceDescriptor->CommandQueueing),
           vendorId,
           productId,
           productRevision,
           serialNumber,
           busType,
           (DeviceDescriptor->RawPropertiesLength ? "Follows\n" : "None\n"));
    if (DeviceDescriptor->RawPropertiesLength != 0) {
        PrintDataBuffer(DeviceDescriptor->RawDeviceProperties,
                        DeviceDescriptor->RawPropertiesLength);
    }
    printf("\n\n");
}



PUCHAR AllocateAlignedBuffer(ULONG size, ULONG AlignmentMask)
{
    PUCHAR ptr;

    // NOTE: This routine does not allow for a way to free
    //       memory.  This is an excercise left for the reader.
    UINT_PTR    align64 = (UINT_PTR)AlignmentMask;

    if (AlignmentMask == 0) {
       ptr = malloc(size);
    } else {
       ptr = malloc(size + AlignmentMask);
       ptr = (PUCHAR)(((UINT_PTR)ptr + align64) & ~align64);
    }
    
    if (ptr == NULL) {
       printf("Memory allocation error.  Terminating program\n");
       exit(1);
    } else {
       return ptr;
    }
}

/**+

Routine Name:

	PrintSptStatus

Description:

	This routine prints a summary of the return status of a DEVICEIOCONTROL call with a 
	function code of IOCTL_SCSI_PASS_THROUGH

Parameters:

	status		[IN} - the boolean status returned by DeviceIoControl()

	cbOut		[IN] - the number of bytes returned in the output buffer by DeviceIoControl()

	pOut		[IN] - a pointer to the output bufferpassed to DeviceIoControl()

    dwOutLen	[IN] - the length in bytes of the output buffer passed to DeviceIoControl()
	
Return value:

	None

--*/
/*	 
VOID PrintSptStatus(BOOL status, DWORD cbOut, PSPTD pSptd, ULONG cbSptd)
{
    ULONG errorCode;
	
	printf(" ");
	PrintError((status) ? 0 : GetLastError());
	switch (pSptd->Type)
	{
		case SPTD_TYPE_DIRECT:
			printf(" SCSI status %02Xh, %d bytes transferred\n",
				    pSptd->Dio.ScsiStatus,
					pSptd->Dio.DataTransferLength);
			return;

		case SPTD_TYPE_BUFFERED:

			printf(" SCSI status %02Xh, %d bytes transferred\n",
				    pSptd->Bio.ScsiStatus,
					pSptd->Bio.DataTransferLength);
			return;

		default:
			BUGCHECK("Invalid descriptor type");
	}
}

BOOL GetAlignmentMaskForDevice(
    IN HANDLE DeviceHandle,
    OUT PULONG AlignmentMask
    )
{
    PSTORAGE_ADAPTER_DESCRIPTOR adapterDescriptor = NULL;
    PSTORAGE_DEVICE_DESCRIPTOR deviceDescriptor = NULL;
    STORAGE_DESCRIPTOR_HEADER header = {0};

    BOOL ok = TRUE;
    BOOL failed = TRUE;
    ULONG i;

    *AlignmentMask = 0; // default to no alignment

    // Loop twice:
    //  First, get size required for storage adapter descriptor
    //  Second, allocate and retrieve storage adapter descriptor
    //  Third, get size required for storage device descriptor
    //  Fourth, allocate and retrieve storage device descriptor
    for (i=0;i<4;i++) {

        PVOID buffer;
        ULONG bufferSize;
        ULONG returnedData;
        
        STORAGE_PROPERTY_QUERY query = {0};

        switch(i) {
            case 0: {
                query.QueryType = PropertyStandardQuery;
                query.PropertyId = StorageAdapterProperty;
                bufferSize = sizeof(STORAGE_DESCRIPTOR_HEADER);
                buffer = &header;
                break;
            }
            case 1: {
                query.QueryType = PropertyStandardQuery;
                query.PropertyId = StorageAdapterProperty;
                bufferSize = header.Size;
                if (bufferSize != 0) {
                    adapterDescriptor = LocalAlloc(LPTR, bufferSize);
                    if (adapterDescriptor == NULL) {
                        goto Cleanup;
                    }
                }
                buffer = adapterDescriptor;
                break;
            }
            case 2: {
                query.QueryType = PropertyStandardQuery;
                query.PropertyId = StorageDeviceProperty;
                bufferSize = sizeof(STORAGE_DESCRIPTOR_HEADER);
                buffer = &header;
                break;
            }
            case 3: {
                query.QueryType = PropertyStandardQuery;
                query.PropertyId = StorageDeviceProperty;
                bufferSize = header.Size;

                if (bufferSize != 0) {
                    deviceDescriptor = LocalAlloc(LPTR, bufferSize);
                    if (deviceDescriptor == NULL) {
                        goto Cleanup;
                    }
                }
                buffer = deviceDescriptor;
                break;
            }
        }

        // buffer can be NULL if the property queried DNE.
        if (buffer != NULL) {
            RtlZeroMemory(buffer, bufferSize);
            
            // all setup, do the ioctl
            ok = DeviceIoControl(DeviceHandle,
                                 IOCTL_STORAGE_QUERY_PROPERTY,
                                 &query,
                                 sizeof(STORAGE_PROPERTY_QUERY),
                                 buffer,
                                 bufferSize,
                                 &returnedData,
                                 FALSE);
            if (!ok) {
                if (GetLastError() == ERROR_MORE_DATA) {
                    // this is ok, we'll ignore it here
                } else if (GetLastError() == ERROR_INVALID_FUNCTION) {
                    // this is also ok, the property DNE
                } else if (GetLastError() == ERROR_NOT_SUPPORTED) {
                    // this is also ok, the property DNE
                } else {
                    // some unexpected error -- exit out
                    goto Cleanup;
                }
                // zero it out, just in case it was partially filled in.
                RtlZeroMemory(buffer, bufferSize);
            }
        }
    } // end i loop

    // adapterDescriptor is now allocated and full of data.
    // deviceDescriptor is now allocated and full of data.
    
    if (adapterDescriptor == NULL) {
        printf("   ***** No adapter descriptor supported on the device *****\n");
    } else {
        PrintAdapterDescriptor(adapterDescriptor);
        *AlignmentMask = adapterDescriptor->AlignmentMask;
    }
    
    if (deviceDescriptor == NULL) {
        printf("   ***** No device descriptor supported on the device  *****\n");
    } else {
        PrintDeviceDescriptor(deviceDescriptor);
    }
    
    failed = FALSE;

Cleanup:
    if (adapterDescriptor != NULL) {
        LocalFree( adapterDescriptor );
    }
    if (deviceDescriptor != NULL) {
        LocalFree( deviceDescriptor );
    }
    return (!failed);

}

/*++

Routine Description:

    Prints formatted error message

Arguments:

    ErrorCode   - Error code to print


Return Value:
    
      None
--*/

VOID PrintError(ULONG ErrorCode)
{
	UCHAR errorBuffer[80];
    ULONG count;

    count = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
                    NULL,
                    ErrorCode,
                    0,
                    errorBuffer,
                    sizeof(errorBuffer),
                    NULL
                    );

    if (count != 0) {
        printf("%s", errorBuffer);
    } else {
        printf("Format message failed.  Error: %d\n", GetLastError());
    }
}

/*++

  SetMemory - set a region of memory to a user supplied data pattern
	

  Arguments:

    pDst		[IN] - Starting address of memory to be filled

	cbCount		[IN] - number of bytes to be filled

	pzPattern	[IN] - pointer to a string containing the fill pattern

 Description

	The pattern provided is repeatedly copied to the memory address
	specified until the entire range of addresses has been filled. The
	string terminator is not copied.

--*/

/*
VOID SetMemory(PUCHAR pDst, DWORD cbCount, LPTSTR pszPattern)
{
	int plen, index;
	
	plen = strlen(pszPattern);
	index = 0;
	while (cbCount--)
	{
		*pDst++ = pszPattern[index++];
		if(index == plen)
			index = 0;
	}
}

/*++

AllocateSPTD
  
	The AllocateSPTD function allocates and initializes a SCSI_PASS_THROUGH_xxxx IOCTL 
    descriptor.

Parameters

	dwType	[IN]			-	a DWORD value specifying the buffer type of the descriptor to be
								allocated

									SPTD_TYPE_BUFFERED - allocate a buffererd I/O descriptor
									SPTD_TYPE_DIRECT   - allocate a direct I/O descriptor

	dwSenseInfoLength [IN]	-	specifies the number of bytes of buffer space to allocate for the
								Sense data buffer and may be zero.

	dwDataTransferSize[IN]	-	specifies the number of bytes of data buffer space to allocate
									for the descriptor and may be zero. The data buffer allocated is
									guaranteed to be DWORD aligned.

Return Values

	A pointer to the newly allocated descriptor or NULL if the call fails.  Use GetLastError()
	to determine the cause of the failure.

Remarks

	The SCSI IOCTL data structures and associated buffers returned by this function are guaranteed
	to be DWORD aligned.

 --*/

/*
PSPTD AllocateSPTD(ULONG dwType, UCHAR ucSenseInfoLength, ULONG dwDataTransferSize)
{							  
	PSPTD	pSptd;
	DWORD	dwBufferSize;
	DWORD	dwBufferOffset;
	DWORD	dwAlignmentFill;

	// Validate parameters

	if((dwType != SPTD_TYPE_DIRECT) && (dwType != SPTD_TYPE_BUFFERED))
	{
			SetLastError(ERROR_INVALID_PARAMETER);
			return 0;
	}
	

	// Allocate SCSI pass-through header and optional buffers making sure the
	// data buffer, if any, is DWORD aligned off the SCSI header

	dwAlignmentFill = dwDataTransferSize ? (ucSenseInfoLength % sizeof(DWORD)) : 0;
	dwBufferSize = ucSenseInfoLength + dwDataTransferSize + dwAlignmentFill;
	pSptd = calloc(sizeof(SPTD) + dwBufferSize, 1);
	if(!pSptd)
	{
		SetLastError(ERROR_NOT_ENOUGH_MEMORY);
		return pSptd;
	}
	pSptd->Type = dwType;

	// Initialize the buffer offsets and lengths in the SCSI pass-through header

    dwBufferOffset = offsetof(SPTD, Buffers) - offsetof(SPTD, Header);
	pSptd->IoctlLength = dwBufferOffset + dwBufferSize;
	if(pSptd->Type = SPTD_TYPE_BUFFERED)
	{
		pSptd->Bio.Length = sizeof(SCSI_PASS_THROUGH);
		if(ucSenseInfoLength)
		{
			pSptd->Bio.SenseInfoOffset =  dwBufferOffset;
			pSptd->Bio.SenseInfoLength = ucSenseInfoLength;
		}
		if(dwDataTransferSize)
		{
			pSptd->Bio.DataTransferLength = dwDataTransferSize;
			pSptd->Bio.DataBufferOffset = dwBufferOffset + ucSenseInfoLength + dwAlignmentFill;
		}
		printf("SCSI_PASS_THROUGH Descriptor\n");
		printf("----------------------------\n");
		printf(" IOCTL Length: %d\n", pSptd->IoctlLength);
		printf(" SCSI Header Length:%d\n", pSptd->Bio.Length);
		printf(" Sense Info Offset: %d\n", pSptd->Bio.SenseInfoOffset);
		printf(" Sense Info Length: %d\n", pSptd->Bio.SenseInfoLength);
		printf(" Data Buffer Offset: %d\n", pSptd->Bio.DataBufferOffset);
		printf(" Data Transfer Length: %d\n", pSptd->Bio.DataTransferLength);
	}
	else
	{
		pSptd->Dio.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
		pSptd->IoctlLength = pSptd->Dio.Length  + dwBufferSize;
		if(ucSenseInfoLength)
		{
			pSptd->Dio.SenseInfoOffset = offsetof(SPTD, Buffers) - offsetof(SPTD, Dio);
			pSptd->Dio.SenseInfoLength = ucSenseInfoLength;
		}
		printf("SCSI_PASS_THROUGH_DIRECT Descriptor\n");
		printf("-----------------------------------\n");
		printf(" IOCTL Length: %d\n", pSptd->IoctlLength);
		printf(" SCSI Header Length:%d\n", pSptd->Dio.Length);
		printf(" Sense Info Offset: %d\n", pSptd->Dio.SenseInfoOffset);
		printf(" Sense Info Length: %d\n", pSptd->Dio.SenseInfoLength);
	}

	// Return the completed descriptor
		(USHORT) (sizeof(SPTD) - offsetof(SPTD, Bio) + dwBufferSize);
	return pSptd;

}
*/
PCHAR
IrpMajorFunctionString (
    UCHAR MajorFunction
)
{
    static char szString[12];

    switch (MajorFunction)
    {
        case IRP_MJ_CREATE:
            return "IRP_MJ_CREATE";

        case IRP_MJ_CREATE_NAMED_PIPE:
            return "IRP_MJ_CREATE_NAMED_PIPE";

        case IRP_MJ_CLOSE:
            return "IRP_MJ_CLOSE";

        case IRP_MJ_READ:
            return "IRP_MJ_READ";

        case IRP_MJ_WRITE:
            return "IRP_MJ_WRITE";

        case IRP_MJ_QUERY_INFORMATION:
            return "IRP_MJ_QUERY_INFORMATION";

        case IRP_MJ_SET_INFORMATION:
            return "IRP_MJ_SET_INFORMATION";

        case IRP_MJ_QUERY_EA:
            return "IRP_MJ_QUERY_EA";

        case IRP_MJ_SET_EA:
            return "IRP_MJ_SET_EA";

        case IRP_MJ_FLUSH_BUFFERS:
            return "IRP_MJ_FLUSH_BUFFERS";

        case IRP_MJ_QUERY_VOLUME_INFORMATION:
            return "IRP_MJ_QUERY_VOLUME_INFORMATION";

        case IRP_MJ_SET_VOLUME_INFORMATION:
            return "IRP_MJ_SET_VOLUME_INFORAMATION";

	case IRP_MJ_DIRECTORY_CONTROL:
	    return "IRP_MJ_DIRECTORY_CONTROL";

        case IRP_MJ_FILE_SYSTEM_CONTROL:
            return "IRP_MJ_FILE_SYSTEM_CONTROL";

        case IRP_MJ_DEVICE_CONTROL:
            return "IRP_MJ_DEVICE_CONTROL";

        case IRP_MJ_INTERNAL_DEVICE_CONTROL:
            return "IRP_MJ_SCSI";

        case IRP_MJ_SHUTDOWN:
            return "IRP_MJ_SHUTDOWN";

        case IRP_MJ_LOCK_CONTROL:
            return "IRP_MJ_LOCK_CONTROL";

        case IRP_MJ_CLEANUP:
            return "IRP_MJ_CLEANUP";

        case IRP_MJ_CREATE_MAILSLOT:
            return "IRP_MJ_CREATE_MAILSLOT";

        case IRP_MJ_QUERY_SECURITY:
            return "IRP_MJ_QUERY_SECURITY";

        case IRP_MJ_SET_SECURITY:
            return "IRP_MJ_SET_SECURITY";

        case IRP_MJ_POWER:
            return "IRP_MJ_POWER";

        case IRP_MJ_SYSTEM_CONTROL:
            return "IRP_MJ_SYSTEM_CONTROL";

        case IRP_MJ_DEVICE_CHANGE:
            return "IRP_MJ_DEVICE_CHANGE";

        case IRP_MJ_QUERY_QUOTA:
            return "IRP_MJ_QUERY_QUOTA";

        case IRP_MJ_SET_QUOTA:
            return "IRP_MJ_SET_QUOTA";

        case IRP_MJ_PNP:
            return "IRP_MJ_PNP";

        default:
            printf(szString, "IRP_MJ_0x%2.2x", MajorFunction);
            return szString;
    }
}

PCHAR ScsiopToString (
    UCHAR Scsiop
)
{
    static char szString[12];

    switch (Scsiop)
    {
       case SCSIOP_TEST_UNIT_READY:
            return "TEST_UNIT_READY";

//      case SCSIOP_REZERO_UNIT:
//          return "REZERO_UNIT";

        case SCSIOP_REWIND:
            return "REWIND";

//        case SCSIOP_REQUEST_BLOCK_ADDR:
//            return "REQUEST_BLOCK_ADDR";

        case 0x02:
            return "VENDOR_SPECIFIC_CDB";

        case SCSIOP_REQUEST_SENSE:
            return "REQUEST_SENSE";

        case SCSIOP_FORMAT_UNIT:
            return "FORMAT_UNIT";

        case SCSIOP_READ_BLOCK_LIMITS:
            return "READ_BLOCK_LIMITS";

        case 0x06:
            return "VENDOR_SPECIFIC_CDB";

        case SCSIOP_REASSIGN_BLOCKS:
            return "REASSIGN_BLOCKS";

//      case SCSIOP_INIT_ELEMENT_STATUS:
//          return "INIT_ELEMENT_STATUS";

        case SCSIOP_READ6:
            return "READ6";

        case 0x09:
            return "VENDOR_SPECIFIC_CDB";

//      case SCSIOP_RECEIVE:
//          return "RECEIVE";

        case SCSIOP_WRITE6:
            return "WRITE6";

//     case SCSIOP_PRINT:
//          return "PRINT";

//      case SCSIOP_SEND:
//          return "SEND";

        case SCSIOP_SEEK6:
            return "SEEK6";

        case 0x0C:
        case 0x0D:
        case 0x0E:
            return "VENDOR_SPECIFIC_CDB";

//        case SCSIOP_TRACK_SELECT:
//            return "TRACK_SELECT";

//      case SCSIOP_SLEW_PRINT:
//          return "SLEW_PRINT";

//        case SCSIOP_SEEK_BLOCK:
//            return "SEEK_BLOCK";

//        case SCSIOP_PARTITION:
//            return "PARTITION";

        case SCSIOP_READ_REVERSE:
            return "READ_REVERSE6";

        case SCSIOP_WRITE_FILEMARKS:
            return "WRITE_FILEMARKS";

//      case SCSIOP_FLUSH_BUFFER:
//          return "FLUSH_BUFFER";

        case SCSIOP_SPACE:
            return "SPACE";

        case SCSIOP_INQUIRY:
            return "INQUIRY";

        case SCSIOP_VERIFY6:
            return "VERIFY6";

        case SCSIOP_RECOVER_BUF_DATA:
            return "RECOVER_BUF_DATA";

        case SCSIOP_MODE_SELECT:
            return "MODE_SELECT";

        case SCSIOP_RESERVE_UNIT:
            return "RESERVE_UNIT";

        case SCSIOP_RELEASE_UNIT:
            return "RELEASE_UNIT";

        case SCSIOP_COPY:
            return "COPY";

        case SCSIOP_ERASE:
            return "ERASE";

        case SCSIOP_MODE_SENSE:
            return "MODE_SENSE";

        case SCSIOP_START_STOP_UNIT:
            return "START_STOP_UNIT";

//      case SCSIOP_STOP_PRINT:
//          return "STOP_PRINT";

//      case SCSIOP_LOAD_UNLOAD:
//          return "LOAD_UNLOAD";

        case SCSIOP_RECEIVE_DIAGNOSTIC:
            return "RECEIVE_DIAGNOSTIC";

        case SCSIOP_SEND_DIAGNOSTIC:
            return "SEND_DIAGNOSTIC";

        case SCSIOP_MEDIUM_REMOVAL:
            return "MEDIUM_REMOVAL";

        case 0x20:
        case 0x21:
        case 0x22:
        case 0x23:
        case 0x24:
            return "VENDOR_SPECIFIC_CDB";

        case SCSIOP_READ_CAPACITY:
            return "READ_CAPACITY";

        case 0x26:
        case 0x27:
	    return "VENDOR_SPECIFIC_CDB";

        case SCSIOP_READ:
            return "READ10";

        case 0x29:
            return "READ_GENERATION";

        case 0x2A:
            return "WRITE10";

        case SCSIOP_SEEK:
            return "SEEK10";

//      case SCSIOP_LOCATE:
//          return "LOCATE";

//      case SCSIOP_POSITION_TO_ELEMENT:
//          return "POSITION_TO_ELEMENT";

        case 0x2D:
            return "VENDOR_SPECIFIC_CDB";

        case SCSIOP_WRITE_VERIFY:
            return "WRITE_AND_VERIFY10";

        case SCSIOP_VERIFY:
            return "VERIFY";

        case SCSIOP_SEARCH_DATA_HIGH:
            return "SEARCH_DATA_HIGH";

        case SCSIOP_SEARCH_DATA_EQUAL:
            return "SEARCH_DATA_EQUAL";

        case SCSIOP_SEARCH_DATA_LOW:
            return "SEARCH_DATA_LOW";

        case SCSIOP_SET_LIMITS:
            return "SET_LIMITS";

        case SCSIOP_READ_POSITION:
            return "READ_POSITION";

        case SCSIOP_SYNCHRONIZE_CACHE:
            return "SYNCHRONIZE_CACHE";

        case 0x36:
             return "LOCK_UNLOCK_CACHE";

        case 0x37:
             return "READ_DEFECT_DATA";

        case 0x38:
            return "MEDIUM_SCAN";
           

        case SCSIOP_COMPARE:
            return "COMPARE";

        case SCSIOP_COPY_COMPARE:
            return "COPY_COMPARE";

        case SCSIOP_WRITE_DATA_BUFF:
            return "WRITE_DATA_BUFF";

        case SCSIOP_READ_DATA_BUFF:
            return "READ_DATA_BUFF";

        case 0x3E:
            return "READ_LONG10";

	case 0x3f:
            return "WRITE_LONG10";
 
        case SCSIOP_CHANGE_DEFINITION:
            return "CHANGE_DEFINITION";

        case SCSIOP_READ_SUB_CHANNEL:
            return "READ_SUB_CHANNEL";

        case SCSIOP_READ_TOC:
            return "READ_TOC";

        case SCSIOP_READ_HEADER:
            return "READ_HEADER";

        case SCSIOP_PLAY_AUDIO:
            return "PLAY_AUDIO";

        case SCSIOP_GET_CONFIGURATION:
            return "GET_CONFIGURATION";

        case SCSIOP_PLAY_AUDIO_MSF:
            return "PLAY_AUDIO_MSF";

        case SCSIOP_PLAY_TRACK_INDEX:
            return "PLAY_TRACK_INDEX";

        case SCSIOP_PLAY_TRACK_RELATIVE:
            return "PLAY_TRACK_RELATIVE";

        case SCSIOP_GET_EVENT_STATUS:
            return "GET_EVENT_STATUS";

        case SCSIOP_PAUSE_RESUME:
            return "PAUSE_RESUME";

        case SCSIOP_LOG_SELECT:
            return "LOG_SELECT";

        case SCSIOP_LOG_SENSE:
            return "LOG_SENSE";

        case SCSIOP_STOP_PLAY_SCAN:
            return "STOP_PLAY_SCAN";

        case 0x50:
            return "XDWRITE10";

        case 0x51:
            return "XPWRITE10";

//      case SCSIOP_READ_DISK_INFORMATION:
//          return "READ_DISK_INFORMATION";

        case 0x52:
            return "XDREAD10";

//      case SCSIOP_READ_TRACK_INFORMATION:
//          return "READ_TRACK_INFORMATION";

        case 0x53:
            return "XDWRITEREAD10";

//      case SCSIOP_RESERVE_TRACK_RZONE:
//          return "RESERVE_TRACK_RZONE";

        case SCSIOP_SEND_OPC_INFORMATION:
            return "SEND_OPC_INFORMATION";

        case SCSIOP_MODE_SELECT10:
            return "MODE_SELECT10";

        case SCSIOP_RESERVE_UNIT10:
            return "RESERVE_UNIT10";

        case SCSIOP_RELEASE_UNIT10:
            return "RELEASE_UNIT10";

        case 0x58:
            return "REPAIR_TRACK";

        case SCSIOP_MODE_SENSE10:
            return "MODE_SENSE10";

        case SCSIOP_CLOSE_TRACK_SESSION:
            return "CLOSE_TRACK_SESSION";

        case SCSIOP_READ_BUFFER_CAPACITY:
            return "READ_BUFFER_CAPACITY";

        case SCSIOP_SEND_CUE_SHEET:
            return "SEND_CUE_SHEET";

        case SCSIOP_PERSISTENT_RESERVE_IN:
            return "PERSISTENT_RESERVE_IN";

        case SCSIOP_PERSISTENT_RESERVE_OUT:
            return "PERSISTENT_RESERVE_OUT";

        case SCSIOP_REPORT_LUNS:
            return "REPORT_LUNS";

        case 0xA1:
            return "ATA_PASSTHROUGH12";

        case SCSIOP_SEND_EVENT:
            return "SEND_EVENT";

        case SCSIOP_SEND_KEY:
            return "SEND_KEY";

        case SCSIOP_REPORT_KEY:
            return "REPORT_KEY";

        case SCSIOP_MOVE_MEDIUM:
            return "MOVE_MEDIUM";

        case SCSIOP_LOAD_UNLOAD_SLOT:
            return "LOAD_UNLOAD_SLOT";

//      case SCSIOP_EXCHANGE_MEDIUM:
//          return "EXCHANGE_MEDIUM";

        case SCSIOP_SET_READ_AHEAD:
            return "SET_READ_AHEAD";

        case SCSIOP_READ_DVD_STRUCTURE:
            return "READ_DVD_STRUCTURE";

        case SCSIOP_REQUEST_VOL_ELEMENT:
            return "REQUEST_VOL_ELEMENT";

        case SCSIOP_SEND_VOLUME_TAG:
            return "SEND_VOLUME_TAG";

        case SCSIOP_READ_ELEMENT_STATUS:
            return "READ_ELEMENT_STATUS";

//        case SCSIOP_READ_CD_MSF:
//            return "READ_CD_MSF";

//        case SCSIOP_SCAN_CD:
//            return "SCAN_CD";

//        case SCSIOP_SET_CD_SPEED:
//            return "SET_CD_SPEED";

//        case SCSIOP_PLAY_CD:
//            return "PLAY_CD";

        case SCSIOP_MECHANISM_STATUS:
            return "MECHANISM_STATUS";

//        case SCSIOP_READ_CD:
//            return "READ_CD";

        case SCSIOP_SEND_DVD_STRUCTURE:
            return "SEND_DVD_STRUCTURE";

//        case SCSIOP_INIT_ELEMENT_RANGE:
//            return "INIT_ELEMENT_RANGE";

        case 0x2C:
            return "ERASE10";

        case 0x41:
            return "WRITE_SAME10";

        case 0x80:
            return "XDWRITE_EXTENDED";

        case 0x81:
            return "REBUILD";

        case 0x82:
            return "REGENERATE";

        case 0x83:
            return "EXTENDED_COPY";

        case 0x84:
            return "RECEIVE_COPY_RESULTS";

        case 0x85:
            return "ATA_PASSTHROUGH16";

        case 0x86:
            return "ACCESS_CONTROL_IN";

        case 0x87:
            return "ACCESS_CONTROL_OUT";

        case 0x88:
            return "READ16";

        case 0x8A:
            return "WRITE16";

        case 0x8C:
            return "READ_ATTRIBUTE";

        case 0x8D:
            return "WRITE_ATTRIBUTE";

        case 0x8e:
            return "WRITE_AND_VERIFY16";

        case 0x8F:
            return "VERIFY16";

        case 0x90:
            return "PRE-FETCH16";

        case 0x91:
            return "SYNCHRONIZE_CACHE16";

        case 0x92:
            return "LOCK-UNLOCK CACHE";

        case 0x93:
            return "WRITE_SAME16";

        case 0xA8:
            return "READ12";

        case 0xAA:
            return "WRITE12";

        case 0xAC:
            return "ERASE12";

        case 0xAE:
            return "WRITE_AND_VERIFY12";

        case 0xAF:
            return "VERIFY12";

        case 0xB0:
            return "SEARCH_DATA_HIGH12";

        case 0xB1:
            return "SEARCH_DATA_EQUAL12";

        case 0xB2:
            return "SEARCH_DATA_LOW12";

        case 0xB3:
             return "SET_LIMITS12";

        case 0xB4:
             return "READ_ELEMENT_STATUS_ATTACHED";

        case 0xB7:
            return "READ_DEFECT_DATA12";

        case 0xB9:
            return "READ_CD_MSF12";

        case 0xBA:
            return "SCAN12";

        case 0xBB:
            return "SET_CDROM_SPEED12";

        case 0xBC:
            return "PLAY_CD12";

        case 0xBE:
            return "READ_CD12";

        default:
            if(Scsiop >= 0xC0)
               return "VENDOR_SPECIFIC_CDB";
            else
               return "UNDEFINED_CDB";
    }
}

PCHAR NtStatusToString(DWORD ntstatus)
{
   static CHAR 		MessageBuffer[512];
   static HMODULE 	Hand;
   CHAR			*cp;

   Hand = LoadLibrary("NTDLL.DLL");
   FormatMessage(
       FORMAT_MESSAGE_FROM_SYSTEM |
       FORMAT_MESSAGE_FROM_HMODULE,
       Hand,
       ntstatus,
       MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
       MessageBuffer,
       sizeof(MessageBuffer),
       NULL );
    FreeLibrary(Hand);
    cp = strchr(MessageBuffer, '\n');
    if (cp)
	*(++cp) = '\0';
    return MessageBuffer;
}

PCHAR GuidToString(LPGUID pGuid)
{
   static CHAR 		GuidBuffer[36];

   sprintf(GuidBuffer, "%8.8X-%4.4X-%4.4X-%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X",
                        pGuid->Data1, pGuid->Data2, pGuid->Data3, pGuid->Data4[0],
                        pGuid->Data4[1],pGuid->Data4[2],pGuid->Data4[3],pGuid->Data4[4],
                        pGuid->Data4[5],pGuid->Data4[6],pGuid->Data4[7],pGuid->Data4[8]);
    return GuidBuffer;
}
  
