/*++

Copyright (c) 1995  Microsoft Corporation

Module Name:

    cdp.c

Abstract:

    A user mode app that allows simple commands to be sent to a
    selected scsi device.

Environment:

    User mode only

Revision History:

    03-26-96 : Created

--*/


#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include <assert.h>

#include <windows.h>
#include <devioctl.h>

#include <ntddscsi.h>

#define _NTSRB_     // to keep srb.h from being included
#include <scsi.h>

#ifdef DBG
#define dbg(x) x
#else
#define dbg(x) /* x */
#endif

#define ARGUMENT_USED(x)    (x == NULL)

typedef struct {
    char *Name;
    char *Description;
    DWORD (*Function)(HANDLE device, int argc, char *argv[]);
} COMMAND;

typedef struct  {
    SCSI_PASS_THROUGH   Spt;
    char                SenseInfoBuffer[18];
    char                DataBuffer[0];          // Allocate buffer space
                                                // after this
} SPT_WITH_BUFFERS, *PSPT_WITH_BUFFERS;

typedef struct {
    UCHAR Reserved1;
    UCHAR ADR : 4;
    UCHAR Control : 4;
    UCHAR TrackNumber;
    UCHAR Reserved2;
    union {
        UCHAR Swap[4];
        ULONG Block;
        struct {
            UCHAR Reserved;
            UCHAR M;
            UCHAR S;
            UCHAR F;
        } MSF;
    } Address;
} TRACK, *PTRACK;


#define MAKE_SPT(p, size, cdbsize, datain, datasize)            \
    size = sizeof(SPT_WITH_BUFFERS) + datasize;                 \
    p = (PSPT_WITH_BUFFERS) malloc(size);                       \
    memset(p, 0, size);                                         \
    p->Spt.Length = sizeof(p->Spt);                             \
    p->Spt.CdbLength = cdbsize;                                 \
    p->Spt.SenseInfoLength = 12;                                \
    p->Spt.DataIn = datain;                                     \
    p->Spt.DataTransferLength = datasize;                       \
    p->Spt.TimeOutValue = 20;                                   \
    p->Spt.SenseInfoOffset =                                    \
        ((DWORD) &(p->SenseInfoBuffer[0]) - (DWORD) (p));       \
    p->Spt.DataBufferOffset =                                   \
        ((DWORD) &(p->DataBuffer[0]) - (DWORD) (p))


DWORD StartStopCommand(HANDLE device, int argc, char *argv[]);
DWORD TestCommand(HANDLE device, int argc, char *argv[]);
DWORD ReadTOCCommand(HANDLE device, int argc, char *argv[]);
DWORD PlayCommand(HANDLE device, int argc, char *argv[]);
DWORD PauseResumeCommand(HANDLE device, int argc, char *argv[]);
DWORD SendCommand(HANDLE device, int argc, char *argv[]);
DWORD ListCommand(HANDLE device, int argc, char *argv[]);


//
// List of commands
// all command names are case sensitive
// arguments are passed into command routines
// list must be terminated with NULL command
// command will not be listed in help if description == NULL
//

COMMAND CommandArray[] = {
    {"eject", "spins down and ejects the specified drive", StartStopCommand},
    {"help", "help for all commands", ListCommand},
    {"load", "loads the specified drive", StartStopCommand},
    {"pause", "pauses audio playback", PauseResumeCommand},
    {"play", "[start track [end track]] plays audio tracks [", PlayCommand},
    {"resume", "resumes paused audio playback", PauseResumeCommand},
    {"send", NULL, SendCommand},
    {"start", "spins up the drive", StartStopCommand},
    {"stop", "spinds down the drive", StartStopCommand},
    {"test", NULL, TestCommand},
    {"toc", "prints the table of contents", ReadTOCCommand},
    {NULL, NULL, NULL}
    };

#define STATUS_SUCCESS 0

int __cdecl main(int argc, char *argv[])
{
    int i = 0;
    HANDLE h;
    char buffer[32];

    if(argc < 3) {
        printf("Usage: cdp <drive> <command> [parameters]\n");
        printf("possible commands: \n");
        ListCommand(NULL, argc, argv);
        printf("\n");
        return -1;
    }

    sprintf(buffer, "\\\\.\\%s", argv[1]);
    dbg(printf("Sending command %s to drive %s\n", argv[2], buffer));

    h = CreateFile(buffer,
                   GENERIC_READ,
                   FILE_SHARE_READ,
                   NULL,
                   OPEN_EXISTING,
                   FILE_ATTRIBUTE_NORMAL,
                   NULL);

    if(h == INVALID_HANDLE_VALUE) {
        printf("Error %d opening device %s\n", GetLastError(), buffer);
        return -2;
    }

    //
    // Iterate through the command array and find the correct function to
    // call.
    //

    while(CommandArray[i].Name != NULL) {
        
        if(strcmp(argv[2], CommandArray[i].Name) == 0) {

            (CommandArray[i].Function)(h, (argc - 2), &(argv[2]));

            break;
        }

        i++;
    }

    if(CommandArray[i].Name == NULL) {
        printf("Unknown command %s\n", argv[2]);
    }

    CloseHandle(h);

    return 0;
}

DWORD StartStopCommand(HANDLE device, int argc, char *argv[])
/*++

Routine Description:

    Sends down a startstop command.

Arguments:
    device - a file handle to send the ioctl to

    argc - the number of additional arguments.  should be zero

    argv[0] - "eject", "load", "start" or "stop"

Return Value: 
    
    STATUS_SUCCESS if successful
    The value of GetLastError() from the point of failure

--*/
{
    PSPT_WITH_BUFFERS spt;
    PCDB cdb;
    DWORD returned;
    DWORD errorValue = STATUS_SUCCESS;
    DWORD packetSize;

    UCHAR loadEject = 0;
    UCHAR start = 0;

    if(strcmp("eject", argv[0]) == 0)  {
        loadEject = 1;
        start = 0;
    } else if(strcmp("load", argv[0]) == 0) {
        loadEject = 1;
        start = 1;
    } else if(strcmp("start", argv[0]) == 0) {
        loadEject = 0;
        start = 1;
    } else if(strcmp("stop", argv[0]) == 0) {
        loadEject = 0;
        start = 0;
    } else {
        assert(0);
    }

    MAKE_SPT(spt, packetSize, 6, 0, 0);

    cdb = (PCDB) &(spt->Spt.Cdb[0]);
    cdb->START_STOP.OperationCode = SCSIOP_START_STOP_UNIT;
    cdb->START_STOP.Immediate = 0;
    cdb->START_STOP.Start = start;
    cdb->START_STOP.LoadEject = loadEject;

    if(!DeviceIoControl(device,
                       IOCTL_SCSI_PASS_THROUGH,
                       spt,
                       sizeof(SPT_WITH_BUFFERS),
                       spt, 
                       sizeof(SPT_WITH_BUFFERS),
                       &returned,
                       FALSE)) {
                   
        errorValue = GetLastError();
        printf("Eject - error sending IOCTL (%d)\n", errorValue);
    }

    free(spt);
    return errorValue;
}

DWORD LoadCommand(HANDLE device, int argc, char *argv[])
/*++

Routine Description:

    Creates an START_STOP pass through ioctl and sends it to the passed in
    file handle

Arguments:
    device - a file handle to send the ioctl to

    argc - the number of additional arguments.  should be zero

    argv - the additional arguments

Return Value: 
    
    STATUS_SUCCESS if successful
    The value of GetLastError() from the point of failure

--*/
{
    PSPT_WITH_BUFFERS spt;
    PCDB cdb;
    DWORD returned;
    DWORD errorValue = STATUS_SUCCESS;
    DWORD packetSize;

    printf("Loading cd\n");

    MAKE_SPT(spt, packetSize, 6, 0, 0);

    cdb = (PCDB) &(spt->Spt.Cdb[0]);
    cdb->START_STOP.OperationCode = SCSIOP_START_STOP_UNIT;
    cdb->START_STOP.Immediate = 0;
    cdb->START_STOP.Start = 1;
    cdb->START_STOP.LoadEject = 1;

    if(!DeviceIoControl(device,
                       IOCTL_SCSI_PASS_THROUGH,
                       spt,
                       packetSize,
                       spt, 
                       packetSize,
                       &returned,
                       FALSE)) {
                   
        errorValue = GetLastError();
        printf("Load - error sending IOCTL (%d)\n", errorValue);
    }

    free(spt); 
    return STATUS_SUCCESS;
}

DWORD ReadTOCCommand(HANDLE device, int argc, char *argv[])
/*++

Routine Description:

    Reads and prints out the cdrom's table of contents

Arguments:
    device - a file handle to send the ioctl to

    argc - the number of additional arguments.  should be zero

    argv - the additional arguments

Return Value: 
    
    STATUS_SUCCESS if successful
    The value of GetLastError() from the point of failure

--*/
{
    PSPT_WITH_BUFFERS spt;
    DWORD errorValue = STATUS_SUCCESS;
    DWORD packetSize, returned = 0;
    PCDB cdb;

    int numTracks, i;
    PTRACK track;
//    DWORD addr;

    printf("Reading Table of Contents\n");

    //
    // Allocate an SPT packet big enough to hold the 4 byte TOC header
    //

    MAKE_SPT(spt, packetSize, 10, 1, 0x04);

    cdb = (PCDB) &(spt->Spt.Cdb[0]);
    cdb->READ_TOC.OperationCode = SCSIOP_READ_TOC;
    cdb->READ_TOC.Msf = 0;
    cdb->READ_TOC.StartingTrack = 0;
    cdb->READ_TOC.AllocationLength[1] = 0x04;

    if(!DeviceIoControl(device,
                       IOCTL_SCSI_PASS_THROUGH,
                       spt,
                       packetSize,
                       spt, 
                       packetSize,
                       &returned,
                       FALSE)) {
        errorValue = GetLastError();
        printf("Error %d sending READ_TOC pass through\n", errorValue);
        goto EndReadTOC;
    } else {
        dbg(printf("READ_TOC pass through returned %d bytes\n", returned));
    }

    printf("TOC Data Length: %d\n", (WORD) *(spt->DataBuffer));
    printf("First Track Number: %d\n", spt->DataBuffer[2]);
    printf("Last Track Number: %d\n", spt->DataBuffer[3]);

    numTracks = spt->DataBuffer[3] - spt->DataBuffer[2] + 1;
    free(spt);

    MAKE_SPT(spt, packetSize, 10, 1, (4 + (numTracks * 8)));

    cdb = (PCDB) &(spt->Spt.Cdb[0]);
    cdb->READ_TOC.OperationCode = SCSIOP_READ_TOC;
    cdb->READ_TOC.Msf = 0;
    cdb->READ_TOC.StartingTrack = 1;
    cdb->READ_TOC.AllocationLength[1] = 0x04 + (numTracks * 8);

    if(!DeviceIoControl(device,
                       IOCTL_SCSI_PASS_THROUGH,
                       spt,
                       packetSize,
                       spt, 
                       packetSize,
                       &returned,
                       FALSE)) {
        errorValue = GetLastError();
        printf("Error %d sending READ_TOC pass through\n", errorValue);
        goto EndReadTOC;
    } else {
        dbg(printf("READ_TOC pass through returned %d bytes\n", returned));
    }

    track = (PTRACK) &(spt->DataBuffer[4]);

    printf("Number    ADR  Control    Address (MSF)\n");
    printf("------    ---  -------    -------------\n");

    for(i = 0; i < numTracks; i++) {

        printf("%6d    %3d  %7d    %3d:%3d:%3d\n", track->TrackNumber,
                                                   track->ADR,
                                                   track->Control,
                                                   track->Address.MSF.M,
                                                   track->Address.MSF.S,
                                                   track->Address.MSF.F);
        track++;
    }


EndReadTOC:

    free(spt);
    return errorValue; 
}

DWORD PlayCommand(HANDLE device, int argc, char *argv[])
/*++

Routine Description:

    Plays an audio track

Arguments:
    device - a file handle to send the ioctl to

    argc - the number of additional arguments.

    argv[1] - the starting track.  Starts at zero if this is not here
    argv[2] - the ending track.  Let track if not specified

Return Value: 
    
    STATUS_SUCCESS if successful
    The value of GetLastError() from the point of failure

--*/
{
    PSPT_WITH_BUFFERS spt;
    DWORD errorValue = STATUS_SUCCESS;
    DWORD packetSize, returned = 0;
    PCDB cdb;

    char startingTrack = 1, endingTrack = -1;

    if(argc > 2) endingTrack = atoi(argv[2]);
    if(argc > 1) startingTrack = atoi(argv[1]);

    printf("Playing from track %d to ", startingTrack);
    if(endingTrack == -1) {
        printf("end of disk\n");
    } else {
        printf("track %d\n", endingTrack);
    }

    //
    // Allocate an SPT packet big enough to hold the 4 byte TOC header
    //

    MAKE_SPT(spt, packetSize, 10, 1, 0);

    //
    // Unfortunately no one defined the PLAY_INDEX command for us so
    // cheat and use MSF
    //

    cdb = (PCDB) &(spt->Spt.Cdb[0]);
    cdb->PLAY_AUDIO_MSF.OperationCode = SCSIOP_PLAY_TRACK_INDEX;
    cdb->PLAY_AUDIO_MSF.StartingS = startingTrack;
    cdb->PLAY_AUDIO_MSF.StartingF = 0;
    cdb->PLAY_AUDIO_MSF.EndingS = endingTrack;
    cdb->PLAY_AUDIO_MSF.EndingF = 0;

    if(!DeviceIoControl(device,
                       IOCTL_SCSI_PASS_THROUGH,
                       spt,
                       packetSize,
                       spt, 
                       packetSize,
                       &returned,
                       FALSE)) {
        errorValue = GetLastError();
        printf("Error %d sending PLAY_AUDIO_INDEX pass through\n", errorValue);
    } else {
        dbg(printf("PLAY_AUDIO_INDEX pass through returned %d bytes\n", returned));
    }

    free(spt);
    return errorValue; 
}

DWORD PauseResumeCommand(HANDLE device, int argc, char *argv[])
/*++

Routine Description:

    pauses or resumes audio playback

Arguments:
    device - a file handle to send the ioctl to

    argc - the number of additional arguments.

    argv[0] - "pause" or "resume" 

Return Value: 
    
    STATUS_SUCCESS if successful
    The value of GetLastError() from the point of failure

--*/
{
    PSPT_WITH_BUFFERS spt;
    DWORD errorValue = STATUS_SUCCESS;
    DWORD packetSize, returned = 0;
    PCDB cdb;

    char pauseResume;

    if(strcmp("pause", argv[0]) == 0)      pauseResume = 0;
    else                                    pauseResume = 1;

    printf("%s cdrom playback\n", (pauseResume ? "Pausing" : "Resuming"));

    //
    // Allocate an SPT packet big enough to hold the 4 byte TOC header
    //

    MAKE_SPT(spt, packetSize, 10, 1, 0);

    //
    // Unfortunately no one defined the PLAY_INDEX command for us so
    // cheat and use MSF
    //

    cdb = (PCDB) &(spt->Spt.Cdb[0]);
    cdb->PAUSE_RESUME.OperationCode = SCSIOP_PAUSE_RESUME;
    cdb->PAUSE_RESUME.Action = pauseResume;

    if(!DeviceIoControl(device,
                       IOCTL_SCSI_PASS_THROUGH,
                       spt,
                       packetSize,
                       spt, 
                       packetSize,
                       &returned,
                       FALSE)) {
        errorValue = GetLastError();
        printf("Error %d sending PAUSE_RESUME pass through\n", errorValue);
    } else {
        dbg(printf("PAUSE_RESUME pass through returned %d bytes\n", returned));
    }

    free(spt);
    return errorValue; 
}

DWORD SendCommand(HANDLE device, int argc, char *argv[])
/*++

Routine Description:

    Parses a hex byte string and creates a cdb to send down.

Arguments:
    device - a file handle to send the ioctl to

    argc - the number of additional arguments.  should be zero

    argv[1] - The CDB to send in a quoted hex byte string
              "47 00 00 00 01 00 00 ff 00 00"

    argv[2] - for data in commands: the number of bytes (decimal) to
              expect from the target

              for data out commands: a quoted byte string containing the
              data to be sent

Return Value: 
    
    STATUS_SUCCESS if successful
    The value of GetLastError() from the point of failure

--*/
{
    PSPT_WITH_BUFFERS spt;
    PCDB cdb;
    DWORD returned;
    DWORD errorValue = STATUS_SUCCESS;
    DWORD packetSize;

    int i;

    UCHAR cdbLength = 0;
    DWORD dataLength = 0;

    MAKE_SPT(spt, packetSize, 6, 0, 0);

    cdb = (PCDB) &(spt->Spt.Cdb[0]);

    //
    // Determine the length of the CDB first
    //

    for(i = 0; i < 12; i++) {

        spt->Spt.Cdb[i];

    }

    if(!DeviceIoControl(device,
                       IOCTL_SCSI_PASS_THROUGH,
                       spt,
                       sizeof(SPT_WITH_BUFFERS),
                       spt, 
                       sizeof(SPT_WITH_BUFFERS),
                       &returned,
                       FALSE)) {
                   
        errorValue = GetLastError();
        printf("Eject - error sending IOCTL (%d)\n", errorValue);
    }

    free(spt);
    return errorValue;
}

DWORD TestCommand(HANDLE device, int argc, char *argv[])
/*++

Routine Description:

    Tests the command "parsing"

Arguments:
    device - a file handle to send the ioctl to

    argc - the number of additional arguments.  should be zero

    argv - the additional arguments

Return Value: 
    
    STATUS_SUCCESS if successful
    The value of GetLastError() from the point of failure

--*/

{
    int i;
    printf("Test - %d additional arguments\n", argc);

    for(i = 0; i < argc; i++) {
        printf("arg %d: %s\n", i, argv[i]);
    }

    return STATUS_SUCCESS; 
}

DWORD ListCommand(HANDLE device, int argc, char *argv[])
/*++

Routine Description:

    Prints out the command list

Arguments:
    device - unused

    argc - unused

    argv - unused

Return Value: 
    
    STATUS_SUCCESS

--*/

{
    int i = 0;

    while(CommandArray[i].Name != NULL) {
    
        if(CommandArray[i].Description != NULL) {
        
            printf("\t%s - %s\n", 
                   CommandArray[i].Name, 
                   CommandArray[i].Description);
        }

        i++;
    }

    return STATUS_SUCCESS; 
}