|
|
/*++
Copyright (c) 1995 Microsoft Corporation
Module Name:
spt.c
Abstract:
A user mode library that allows simple commands to be sent to a selected scsi device.
Environment:
User mode only
Revision History:
4/10/2000 - created
--*/
#include "sptlibp.h"
//
// this routine allows safer DeviceIoControl, specifically handling
// overlapped handles. however, IOCTL_SCSI_PASS_THROUGH and
// IOCTL_SCSI_PASS_THROUGH_DIRECT are blocking calls, so there is
// no support for overlapped IO
//
BOOL SptpSaferDeviceIoControl( IN HANDLE VolumeHandle, IN DWORD IoControlCode, IN LPVOID InBuffer, IN DWORD InBufferSize, IN LPVOID OutBuffer, IN DWORD OutBufferSize, OUT LPDWORD BytesReturned );
BOOL SptUtilValidateCdbLength( IN PCDB Cdb, IN UCHAR CdbSize ) { UCHAR commandGroup = (Cdb->AsByte[0] >> 5) & 0x7;
switch (commandGroup) { case 0: return (CdbSize == 6); case 1: case 2: return (CdbSize == 10); case 5: return (CdbSize == 12); default: return TRUE; } }
BOOL SptSendCdbToDevice( IN HANDLE DeviceHandle, IN PCDB Cdb, IN UCHAR CdbSize, IN PUCHAR Buffer, IN OUT PDWORD BufferSize, IN BOOLEAN GetDataFromDevice ) { return SptSendCdbToDeviceEx(DeviceHandle, Cdb, CdbSize, Buffer, BufferSize, NULL, 0, GetDataFromDevice, SPT_DEFAULT_TIMEOUT ); }
/*++
Routine Description:
Arguments:
Return Value:
--*/ BOOL SptSendCdbToDeviceEx( IN HANDLE DeviceHandle, IN PCDB Cdb, IN UCHAR CdbSize, IN OUT PUCHAR Buffer, IN OUT PDWORD BufferSize, OUT PSENSE_DATA SenseData OPTIONAL, IN UCHAR SenseDataSize, IN BOOLEAN GetDataFromDevice, IN DWORD TimeOut // in seconds
) { PSPTD_WITH_SENSE p; DWORD packetSize; DWORD returnedBytes; BOOL returnValue; PSENSE_DATA senseBuffer;
if ((SenseDataSize == 0) && (SenseData != NULL)) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; }
if ((SenseDataSize != 0) && (SenseData == NULL)) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; }
if (SenseData && SenseDataSize) { RtlZeroMemory(SenseData, SenseDataSize); }
if (Cdb == NULL) { // cannot send NULL cdb
SetLastError(ERROR_INVALID_PARAMETER); return FALSE; }
if (CdbSize < 1 || CdbSize > 16) { // Cdb size too large or too small for this library currently
SetLastError(ERROR_INVALID_PARAMETER); return FALSE; }
if (!SptUtilValidateCdbLength(Cdb, CdbSize)) { // OpCode Cdb->AsByte[0] is not size CdbSize
SetLastError(ERROR_INVALID_PARAMETER); return FALSE; }
if (BufferSize == NULL) { // BufferSize pointer cannot be NULL
SetLastError(ERROR_INVALID_PARAMETER); return FALSE; }
if ((*BufferSize != 0) && (Buffer == NULL)) { // buffer cannot be NULL if *BufferSize is non-zero
SetLastError(ERROR_INVALID_PARAMETER); return FALSE; }
if ((*BufferSize == 0) && (Buffer != NULL)) { // buffer must be NULL if *BufferSize is zero
SetLastError(ERROR_INVALID_PARAMETER); return FALSE; }
if ((*BufferSize) && GetDataFromDevice) {
//
// pre-zero output buffer (not input buffer)
//
memset(Buffer, 0, (*BufferSize)); }
packetSize = sizeof(SPTD_WITH_SENSE); if (SenseDataSize > sizeof(SENSE_DATA)) { packetSize += SenseDataSize - sizeof(SENSE_DATA); }
p = (PSPTD_WITH_SENSE)LocalAlloc(LPTR, packetSize); if (p == NULL) { // could not allocate memory for pass-through
SetLastError(ERROR_NOT_ENOUGH_MEMORY); return FALSE; }
//
// this has the side effect of pre-zeroing the output buffer
// if DataIn is TRUE, the SenseData (always), etc.
//
memset(p, 0, packetSize); memcpy(p->Sptd.Cdb, Cdb, CdbSize);
p->Sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); p->Sptd.CdbLength = CdbSize; p->Sptd.SenseInfoLength = SenseDataSize;
if (*BufferSize != 0) { if (GetDataFromDevice) { p->Sptd.DataIn = SCSI_IOCTL_DATA_IN; // from device
} else { p->Sptd.DataIn = SCSI_IOCTL_DATA_OUT; // to device
} } else { p->Sptd.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED; }
p->Sptd.DataTransferLength = (*BufferSize); p->Sptd.TimeOutValue = TimeOut; p->Sptd.DataBuffer = Buffer; p->Sptd.SenseInfoOffset = FIELD_OFFSET(SPTD_WITH_SENSE, SenseData);
returnedBytes = 0; returnValue = SptpSaferDeviceIoControl(DeviceHandle, IOCTL_SCSI_PASS_THROUGH_DIRECT, p, packetSize, p, packetSize, &returnedBytes);
*BufferSize = p->Sptd.DataTransferLength;
senseBuffer = &(p->SenseData);
if (senseBuffer->SenseKey & 0xf) {
UCHAR length;
// determine appropriate length to return
length = senseBuffer->AdditionalSenseLength; length += RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA, AdditionalSenseLength); if (length > SENSE_BUFFER_SIZE) { length = SENSE_BUFFER_SIZE; } length = min(length, SenseDataSize);
// copy the sense data back to the user regardless
RtlCopyMemory(SenseData, senseBuffer, length); returnValue = FALSE; // some error (possibly recovered) occurred
} else if (p->Sptd.ScsiStatus != 0) { // scsi protocol error
UCHAR length;
// determine appropriate length to return
length = senseBuffer->AdditionalSenseLength; length += RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA, AdditionalSenseLength); if (length > SENSE_BUFFER_SIZE) { length = SENSE_BUFFER_SIZE; } length = min(length, SenseDataSize);
// copy the sense data back to the user regardless
RtlCopyMemory(SenseData, senseBuffer, length); returnValue = FALSE; // some error (possibly recovered) occurred
} else if (!returnValue) {
// returnValue = returnValue;
} else {
// success!
}
//
// free our memory and return
//
LocalFree(p); return returnValue; }
/*++
Routine Description:
NOTE: we default to RETRY==TRUE except for known error classes
Arguments:
Return Value:
--*/ VOID SptUtilInterpretSenseInfo( IN PSENSE_DATA SenseData, IN UCHAR SenseDataSize, OUT PDWORD ErrorValue, // from WinError.h
OUT PBOOLEAN SuggestRetry OPTIONAL, OUT PDWORD SuggestRetryDelay OPTIONAL ) { DWORD error; DWORD retryDelay; BOOLEAN retry; UCHAR senseKey; UCHAR asc; UCHAR ascq;
if (SenseDataSize == 0) { retry = FALSE; retryDelay = 0; error = ERROR_IO_DEVICE; goto SetAndExit;
}
//
// default to suggesting a retry in 1/10 of a second,
// with a status of ERROR_IO_DEVICE.
//
retry = TRUE; retryDelay = 1; error = ERROR_IO_DEVICE;
//
// if the device didn't provide any sense this time, return.
//
if ((SenseData->SenseKey & 0xf) == 0) { retry = FALSE; retryDelay = 0; error = ERROR_SUCCESS; goto SetAndExit; }
//
// if we can't even see the sense key, just return.
// can't use bitfields in these macros, so use next field.
//
if (SenseDataSize < FIELD_OFFSET(SENSE_DATA, Information)) { goto SetAndExit; }
senseKey = SenseData->SenseKey;
{ // set the size to what's actually useful.
UCHAR validLength; // figure out what we could have gotten with a large sense buffer
if (SenseDataSize < RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA, AdditionalSenseLength)) { validLength = SenseDataSize; } else { validLength = RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA, AdditionalSenseLength); validLength += SenseData->AdditionalSenseLength; } // use the smaller of the two values.
SenseDataSize = min(SenseDataSize, validLength); }
if (SenseDataSize < RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA, AdditionalSenseCode)) { asc = SCSI_ADSENSE_NO_SENSE; } else { asc = SenseData->AdditionalSenseCode; }
if (SenseDataSize < RTL_SIZEOF_THROUGH_FIELD(SENSE_DATA, AdditionalSenseCodeQualifier)) { ascq = SCSI_SENSEQ_CAUSE_NOT_REPORTABLE; // 0x00
} else { ascq = SenseData->AdditionalSenseCodeQualifier; }
//
// interpret :P
//
switch (senseKey & 0xf) {
case SCSI_SENSE_RECOVERED_ERROR: { // 0x01
if (SenseData->IncorrectLength) { error = ERROR_INVALID_BLOCK_LENGTH; } else { error = ERROR_SUCCESS; } retry = FALSE; break; } // end SCSI_SENSE_RECOVERED_ERROR
case SCSI_SENSE_NOT_READY: { // 0x02
error = ERROR_NOT_READY;
switch (asc) {
case SCSI_ADSENSE_LUN_NOT_READY: {
switch (ascq) {
case SCSI_SENSEQ_BECOMING_READY: case SCSI_SENSEQ_OPERATION_IN_PROGRESS: { retryDelay = SPT_NOT_READY_RETRY_INTERVAL; break; }
case SCSI_SENSEQ_CAUSE_NOT_REPORTABLE: case SCSI_SENSEQ_FORMAT_IN_PROGRESS: case SCSI_SENSEQ_LONG_WRITE_IN_PROGRESS: { retry = FALSE; break; }
case SCSI_SENSEQ_MANUAL_INTERVENTION_REQUIRED: { retry = FALSE; break; }
} // end switch (senseBuffer->AdditionalSenseCodeQualifier)
break; }
case SCSI_ADSENSE_NO_MEDIA_IN_DEVICE: { error = ERROR_NOT_READY; retry = FALSE; break; } } // end switch (senseBuffer->AdditionalSenseCode)
break; } // end SCSI_SENSE_NOT_READY
case SCSI_SENSE_MEDIUM_ERROR: { // 0x03
error = ERROR_CRC; retry = FALSE;
//
// Check if this error is due to unknown format
//
if (asc == SCSI_ADSENSE_INVALID_MEDIA) {
switch (ascq) {
case SCSI_SENSEQ_UNKNOWN_FORMAT: { error = ERROR_UNRECOGNIZED_MEDIA; break; }
case SCSI_SENSEQ_CLEANING_CARTRIDGE_INSTALLED: { error = ERROR_UNRECOGNIZED_MEDIA; //error = ERROR_CLEANER_CARTRIDGE_INSTALLED;
break; }
} // end switch AdditionalSenseCodeQualifier
} // end SCSI_ADSENSE_INVALID_MEDIA
break; } // end SCSI_SENSE_MEDIUM_ERROR
case SCSI_SENSE_ILLEGAL_REQUEST: { // 0x05
error = ERROR_INVALID_FUNCTION; retry = FALSE;
switch (asc) {
case SCSI_ADSENSE_ILLEGAL_BLOCK: { error = ERROR_SECTOR_NOT_FOUND; break; }
case SCSI_ADSENSE_INVALID_LUN: { error = ERROR_FILE_NOT_FOUND; break; }
case SCSI_ADSENSE_COPY_PROTECTION_FAILURE: { error = ERROR_FILE_ENCRYPTED; //error = ERROR_SPT_LIB_COPY_PROTECTION_FAILURE;
switch (ascq) { case SCSI_SENSEQ_AUTHENTICATION_FAILURE: //error = ERROR_SPT_LIB_AUTHENTICATION_FAILURE;
break; case SCSI_SENSEQ_KEY_NOT_PRESENT: //error = ERROR_SPT_LIB_KEY_NOT_PRESENT;
break; case SCSI_SENSEQ_KEY_NOT_ESTABLISHED: //error = ERROR_SPT_LIB_KEY_NOT_ESTABLISHED;
break; case SCSI_SENSEQ_READ_OF_SCRAMBLED_SECTOR_WITHOUT_AUTHENTICATION: //error = ERROR_SPT_LIB_SCRAMBLED_SECTOR;
break; case SCSI_SENSEQ_MEDIA_CODE_MISMATCHED_TO_LOGICAL_UNIT: //error = ERROR_SPT_LIB_REGION_MISMATCH;
break; case SCSI_SENSEQ_LOGICAL_UNIT_RESET_COUNT_ERROR: //error = ERROR_SPT_LIB_RESETS_EXHAUSTED;
break; } // end switch of ASCQ for COPY_PROTECTION_FAILURE
break; }
} // end switch (senseBuffer->AdditionalSenseCode)
break;
} // end SCSI_SENSE_ILLEGAL_REQUEST
case SCSI_SENSE_DATA_PROTECT: { // 0x07
error = ERROR_WRITE_PROTECT; retry = FALSE; break; } // end SCSI_SENSE_DATA_PROTECT
case SCSI_SENSE_BLANK_CHECK: { // 0x08
error = ERROR_NO_DATA_DETECTED; break; } // end SCSI_SENSE_BLANK_CHECK
case SCSI_SENSE_NO_SENSE: { // 0x00
if (SenseData->IncorrectLength) { error = ERROR_INVALID_BLOCK_LENGTH; retry = FALSE; } else { error = ERROR_IO_DEVICE; } break; } // end SCSI_SENSE_NO_SENSE
case SCSI_SENSE_HARDWARE_ERROR: // 0x04
case SCSI_SENSE_UNIT_ATTENTION: // 0x06
case SCSI_SENSE_UNIQUE: // 0x09
case SCSI_SENSE_COPY_ABORTED: // 0x0A
case SCSI_SENSE_ABORTED_COMMAND: // 0x0B
case SCSI_SENSE_EQUAL: // 0x0C
case SCSI_SENSE_VOL_OVERFLOW: // 0x0D
case SCSI_SENSE_MISCOMPARE: // 0x0E
case SCSI_SENSE_RESERVED: // 0x0F
default: { error = ERROR_IO_DEVICE; break; }
} // end switch(SenseKey)
SetAndExit:
if (ARGUMENT_PRESENT(SuggestRetry)) { *SuggestRetry = retry; } if (ARGUMENT_PRESENT(SuggestRetryDelay)) { *SuggestRetryDelay = retryDelay; } *ErrorValue = error;
return;
}
/*++
Routine Description: Locks the device for exclusive access. Uses the same method format and chkdsk use to gain exclusive access to the volume.
Arguments: VolumeHandle - Handle to the volume. Typically created using CreateFile() to a device of the format \\.\D: ForceDismount - If TRUE, will try to force dismount the disk without prompting the user. Quiet - If TRUE, will not prompt the user. Can be used to fail if the volume is already opened without providing the user an opportunity to force the volume to dismount
Return Value:
--*/ BOOL SptUtilLockVolumeByHandle( IN HANDLE VolumeHandle, IN BOOLEAN ForceDismount ) { ULONG tmp; BOOL succeeded;
tmp = 0; succeeded = SptpSaferDeviceIoControl(VolumeHandle, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &tmp);
// if we locked the volume successfully, or the user wants to force
// the FS to become invalid, mark it as such so when the handle closes
// the FS reverifies the file system.
// if the lock failed and we're not forcing the issue, the routine
// will fail.
if (succeeded || ForceDismount) {
tmp = 0; succeeded = SptpSaferDeviceIoControl(VolumeHandle, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0, &tmp); }
return succeeded; }
/*++
Routine Description:
Arguments:
Return Value:
--*/ BOOL SptpSaferDeviceIoControl( IN HANDLE VolumeHandle, IN DWORD IoControlCode, IN LPVOID InBuffer, IN DWORD InBufferSize, IN LPVOID OutBuffer, IN DWORD OutBufferSize, OUT LPDWORD BytesReturned ) { BOOL succeeded; OVERLAPPED overlapped;
RtlZeroMemory(&overlapped, sizeof(OVERLAPPED)); overlapped.hEvent = CreateEvent(NULL, // default SD
TRUE, // must be manually reset
FALSE, // initially unset
NULL); // unnamed event
if (overlapped.hEvent == NULL) { return FALSE; }
succeeded = DeviceIoControl(VolumeHandle, IoControlCode, InBuffer, InBufferSize, OutBuffer, OutBufferSize, BytesReturned, &overlapped);
if (!succeeded && (GetLastError() == ERROR_IO_PENDING)) { succeeded = GetOverlappedResult( VolumeHandle, &overlapped, BytesReturned, TRUE); }
CloseHandle( overlapped.hEvent ); overlapped.hEvent = NULL;
return succeeded; }
|