ftp.nice.ch/pub/next/tools/scsi/SCSIInquire.3.0.s.tar.gz#/Inquirer/SCSI.m

This is SCSI.m in view mode; [Download] [Up]

//
// SCSI.m
//
// SCSI generic driver object library (phew)
// Version 3 (Revision 3.0)
//
// The SCSI class source, header, and documentation are all 
// Copyright (C) 1990 by Jiro Nakamura and Canon, Inc.
// Copyright (C) 1991 by Jiro Nakamura and Canon, Inc.
// Copyright (C) 1992 by Jiro Nakamura
// All Rights Reserved. 
//
//
// Original Author:	Jiro Nakamura 
// Created:		June 12, 1990
// Last Modified:	July 12, 1991
//
// RCS Information
// Revision Number->	$Revision: 3.1 $
// Last Revised->	$Date: 92/11/13 02:31:51 $
//

static char rcsid[]="$Id: SCSI.m,v 3.1 92/11/13 02:31:51 jiro Exp Locker: jiro $";
static char copyrightid[]="$Copyright: Copyright (C) 1992 by Jiro Nakamura$";

#define VERSION		3

#import "SCSI.h"
#import <libc.h>
#import <stdio.h>
#import <fcntl.h>
#import <stdio.h>
#import <objc/typedstream.h>
#import <appkit/nextstd.h>	// For NXLogError


#import <appkit/Application.h>
#import <appkit/Panel.h>

// SCSI produces a whole truckload of debugging code. Usually we don't
// want it on.
#ifdef DEBUG
	#define DEBUG2
	#undef DEBUG
#endif

@implementation SCSI

+new
{	
	self = [super new];
	target = -1;
	lun = 0;
 	dev_name = "/dev/sg";		// last digit gets appended later
	scsiOpen = FALSE;
	return self;
	return self;
}

+ (void )clearCommandBlock: (union cdb *) cd
{
	int i;
	char *p;
	
	p = (char *)cd;
	for(i=0; i<sizeof(union cdb); i++)
		*p++ = 0;
	return;
}

- (int) version
{
	return VERSION;
}

- (int) openSCSI
{
	char tmpBuf[20];
	unsigned int tmp;
	
	// *No* program should try to open a driver that's already open.
	//  Bad, bad, bad.
	if( scsiOpen == TRUE)
		{
		sprintf(errorString, "SCSI Device already open -- "
			"bug in program...");
		return fd;
		}
	
	for( tmp = 0;  ; tmp ++)
		{
		sprintf(tmpBuf, "%s%1X", dev_name, tmp)	;
		#ifdef DEBUG
			fprintf(stderr, "SCSI: Opening device: %s\n",
				tmpBuf);
		#endif
		if( access(tmpBuf, F_OK) )	// no existo
			break;		
		if( access(tmpBuf, R_OK | W_OK) )	// cannot access
			continue;		
		
		if ((fd = open(tmpBuf, O_RDWR)) >= 0) 
			{
			scsiOpen = TRUE;
			return 0;
			}
		}
	

	sprintf(errorString,"Could not open SCSI "
		"generic driver <%sX>.\n"
		"File descriptor = 0x%X\n", tmpBuf, 
		(unsigned int) fd);
	perror("open");
	scsiOpen = FALSE;
	return -1;
}

- (int) openSCSIAt: (int) trg lun: (int) ln
{
	if( [self openSCSI] )
		return -1;
	
	return(  [self setTarget: trg lun: ln]);
}


- (int) setTarget: (int) trg lun: (int) ln
{
	sa.sa_target = target = trg;
	sa.sa_lun = lun = ln;
	
	if (ioctl(fd,SGIOCSTL,&sa) < 0) 
		{
		sprintf(errorString,"ioctl(SGIOCSTL): "
			"Error setting target %d lun %d (errno = %d)\n",
			target,lun, errno);
		return -1;
		}
	return 0;
}



- (int) closeSCSI
{	
	if( scsiOpen == FALSE)
		{
		sprintf(errorString, "SCSI device already closed. "
				"Error in program...");
		return -1;
		}
		
	if (   close(fd) < 0) 
		{
		sprintf(errorString, 	"Could not close %sX - fd = %XH\n"
			"errno = %d\n", dev_name, 
			(unsigned int) fd ,errno);
		perror("close");
		return -1;
		}	
	scsiOpen = FALSE;
	return 0;
}

- (BOOL) scsiOpen
{
	return( scsiOpen);
}

- (struct scsi_req *) statusReq
{
	return &sr;
}

- (char *) errorString
{
	return errorString;
}

- (int) findDevice
{
	return [self findDevice: 0];
}

- (int) findDevice: (int) trg
{
	int tmp;
	
	if( scsiOpen)
		return [self findDeviceSCSI: trg];
	else
		{
		if([self openSCSI] )
			{
			sprintf(errorString,"Can't open SCSI driver");
			return -1;
			}		
		tmp = [self findDeviceSCSI: trg];
		[self closeSCSI];
		return( tmp);
		}
}

- (int) findDeviceSCSI: (int) trg
{
	int tmp, oldTarg, oldLun;

	oldTarg = target;
	oldLun = lun;
	
	for( tmp = trg; tmp < 8; tmp ++)
		{
		if( [self setTarget: tmp lun: 0] )
			continue;		// no device

		if( [self isDeviceSCSI])
			{
			[self setTarget: oldTarg lun: oldLun];
			return( tmp);
			}
		}

	[self setTarget: oldTarg lun: oldLun];
	return( -1);
}

- (BOOL) isDevice
{
	BOOL tmp;
	
	if( scsiOpen)
		return [self isDeviceSCSI];
	if([self openSCSI] )
		{
		sprintf(errorString,"SCSI: Can't open SCSI driver");
		NXLogError(errorString);
		return NO;
		}
	tmp = [self isDeviceSCSI];
	[self closeSCSI];
	return( tmp);
}


- (BOOL) isDeviceSCSI	// Subclasses should implement this
{
	[self subclassResponsibility: _cmd];
	return NO;
}



//  =====================================================
// 	Archiving methods
//  =====================================================

- write: (NXTypedStream *) stream
{
	[super write: stream];
	NXWriteTypes( stream, "ii**", &target, &lun, &dev_name, &errorString);
	return self;
}

- read: (NXTypedStream *) stream
{
	[super read: stream];
	NXReadTypes( stream, "ii**", &target, &lun, &dev_name, &errorString);
	return self;
}


//  =====================================================
// 	Internal Routines
//  =====================================================


- (int) performSCSIRequest
{
	if (ioctl(fd,SGIOCREQ, &sr) < 0) {
		#ifdef DEBUG
			printf("..Error executing ioctl\n");
			printf("errno = %d\n",errno);
		#endif
		return errno;
	}
	
	#ifdef DEBUG
		fprintf(stderr,
			"SCSI Command %x ==> %d byte(s) in %d.%06d sec\n", 
			sr.sr_cdb.cdb_c6.c6_opcode,
			sr.sr_dma_xfr,
			(int) sr.sr_exec_time.tv_sec, 
			(int) sr.sr_exec_time.tv_usec);
	#endif
	
	if(sr.sr_io_status || sr.sr_scsi_status) {
		#ifdef DEBUG2
		fprintf(stderr, "sr_io_status = 0x%02X\n", (unsigned int) 
			sr.sr_io_status);
		if(sr.sr_io_status == SR_IOST_CHKSV) {
			fprintf(stderr,
				"   sense key = 0x%02X   sense code = %02XH\n",
				(unsigned int) sr.sr_esense.er_sensekey,
				(unsigned int) sr.sr_esense.er_addsensecode);
			}
		fprintf(stderr, "SCSI status = 0x%02X\n", (unsigned int) 
			sr.sr_scsi_status);
		#endif
		return sr.sr_io_status;
	}
	
	return 0;
} 



//  =====================================================
// 	SCSI ROUTINES AND MACROS
//  =====================================================




- (int) inquiry: (struct inquiry_reply *) ibuffer
{
	int tmp;
	
	if( scsiOpen)
		return [self inquirySCSI: ibuffer];
	else
		{
		if([self openSCSI] )
			{
			sprintf(errorString,"Can't open SCSI driver");
			return -1;
			}
		
		tmp = [self inquirySCSI: ibuffer];
		[self closeSCSI];
		return( tmp);
		}
}


- (int) inquirySCSI: (struct inquiry_reply *) ibuffer
{	
	struct cdb_6 *cdbp = &sr.sr_cdb.cdb_c6;

	[SCSI clearCommandBlock: (union cdb *) cdbp];
	cdbp->c6_opcode = C6OP_INQUIRY;
	cdbp->c6_lun    = lun;
	cdbp->c6_len	= 36;
	sr.sr_dma_dir	= SR_DMA_RD;
	sr.sr_addr	= (char *) ibuffer;
	sr.sr_dma_max   = 36;
	sr.sr_ioto	= 10;

	return [self performSCSIRequest];
}

- (int) readCapacity: (struct capacity_reply *) rbuffer
{
	int tmp;
	
	if( scsiOpen)
		{
		return [self readCapacitySCSI: rbuffer];
		}
	else
		{
		if([self	openSCSI] )
			{
			sprintf(errorString,"Can't open SCSI driver");
			return -1;
			}
		tmp = [self readCapacitySCSI: rbuffer];
		[self closeSCSI];
		return( tmp);
		}
}

- (int) readCapacitySCSI: (struct capacity_reply *) rbuffer
{	
	struct cdb_10 *cdbp = &sr.sr_cdb.cdb_c10;

	[SCSI clearCommandBlock: (union cdb *) cdbp];
	cdbp->c10_opcode = C10OP_READCAPACITY;
	cdbp->c10_lun    = lun;
	cdbp->c10_len	= 0;
	sr.sr_dma_dir	= SR_DMA_RD;
	sr.sr_addr	= (char *) rbuffer;
	sr.sr_dma_max   = 8;
	sr.sr_ioto	= 10;
	
	return [self performSCSIRequest];
}


- (int)	requestSense: (union esenseReply *)  rbuffer
{
	int tmp;
	if( scsiOpen)
		return [self requestSenseSCSI: rbuffer];
	else
		{
		if([self	openSCSI] )
			{
			sprintf(errorString,"Can't open SCSI driver");
			return -1;
			}
		tmp = [self requestSenseSCSI: rbuffer];
		[self closeSCSI];
		return( tmp);
		}
}




- (int)	requestSenseSCSI:	(union esenseReply *)  buffer
{
	struct cdb_6 *cdbp = &sr.sr_cdb.cdb_c6;

	[SCSI clearCommandBlock: (union cdb *) cdbp];
	cdbp->c6_opcode = C6OP_REQSENSE;
	cdbp->c6_lun    = lun;
	cdbp->c6_len	= sizeof(*buffer);
	sr.sr_dma_dir	= SR_DMA_RD;
	sr.sr_addr	= (char *) buffer;
	sr.sr_dma_max   = sizeof(*buffer);
	sr.sr_ioto	= 10;

	return [self performSCSIRequest];
}


- (int)	receiveDiagnosticAlloc: (int)  alloc   buffer: (char *) buffer
{
	int tmp;
	if( scsiOpen)
		return [self receiveDiagnosticSCSIAlloc:  alloc  
			 buffer: buffer];
	else
		{
		if([self	openSCSI] )
			{
			sprintf(errorString,"Can't open SCSI driver");
			return -1;
			}
		tmp = [self receiveDiagnosticSCSIAlloc:  alloc  
			 buffer: buffer];
		[self closeSCSI];
		return( tmp);
		}
}


- (int)	receiveDiagnosticSCSIAlloc: (int)  alloc   buffer: (char *) buffer
{
	struct cdb_6 *cdbp = &sr.sr_cdb.cdb_c6;

	[SCSI clearCommandBlock: (union cdb *) cdbp];
	cdbp->c6_opcode = C6OP_RECEIVEDIAG;
	cdbp->c6_lun    = lun;
	cdbp->c6_len	= alloc;
	sr.sr_dma_dir	= SR_DMA_RD;
	sr.sr_addr	= (char *) buffer;
	sr.sr_dma_max   = alloc;
	sr.sr_ioto	= 10;

	return [self performSCSIRequest];
}

- (int)	sendDiagnosticPf: (BOOL) pf selfTest: (BOOL) st 
	deviceOffLine: (BOOL) dol unitOffLine: (BOOL) uol
	parameterListLength: (int) pll parameterBuffer: (char *) pbuf
{
	int tmp;
	if( scsiOpen)
		return [self sendDiagnosticSCSIPf:  pf selfTest: st 
			deviceOffLine:  dol unitOffLine:  uol
			parameterListLength: pll parameterBuffer: pbuf];
	else
		{
		if([self openSCSI] )
			{
			sprintf(errorString,"Can't open SCSI driver");
			return -1;
			}
		tmp = [self sendDiagnosticSCSIPf:  pf selfTest: st 
			deviceOffLine:  dol unitOffLine:  uol
			parameterListLength: pll parameterBuffer: pbuf];
		[self closeSCSI];
		return( tmp);
		}
}


- (int) sendDiagnosticSCSIPf: (BOOL) pf selfTest: (BOOL) st 
	deviceOffLine: (BOOL) dol unitOffLine: (BOOL) uol
	parameterListLength: (int) pll parameterBuffer: (char *) pbuf
{
	struct cdb_6 *cdbp = &sr.sr_cdb.cdb_c6;	
	 		  
	[SCSI clearCommandBlock: (union cdb *) cdbp];
	cdbp->c6_opcode = C6OP_SENDDIAG;
	cdbp->c6_lun    = lun;
	cdbp->c6_lba	= 	
				(pf << 20) + 
				(st << 18) + 
				(dol << 17) +
				(uol << 16) +
				((((unsigned int) pll & 0xff00) >> 8)
					 & 0x00ff);	
			
	cdbp->c6_len 	= (unsigned int) pll & 0x00ff;
	sr.sr_dma_dir	= SR_DMA_WR;
	sr.sr_addr	=  pbuf;	
	sr.sr_dma_max   = pll;
	sr.sr_ioto	= 10;

	#ifdef DEBUG
		printf("Send diagnostic\n");
		printf("LBA  = %08X\n", cdbp->c6_lba);
		printf("Len  = %04X\n", cdbp->c6_len);
		printf("St  = %d\n", st);
	#endif
	
	return [self performSCSIRequest];
}



- (int)	testUnitReady
{
	int tmp;
	if( scsiOpen)
		return [self testUnitReadySCSI];
	else
		{
		if([self openSCSI] )
			{
			sprintf(errorString,"Can't open SCSI driver");
			return -1;
			}
		tmp = [self testUnitReadySCSI];
		[self closeSCSI];
		return( tmp);
		}
}


- (int) testUnitReadySCSI
{	
	struct cdb_6 *cdbp = &sr.sr_cdb.cdb_c6;

	[SCSI clearCommandBlock: (union cdb *) cdbp];
	cdbp->c6_opcode = C6OP_TESTRDY;
	cdbp->c6_lun    = lun;
	cdbp->c6_len	= 0;
	sr.sr_dma_dir	= SR_DMA_RD;
	sr.sr_addr	= (char *) 0;
	sr.sr_dma_max   = 0;
	sr.sr_ioto	= 10;

	return([self performSCSIRequest]);
}


- (int) modeSensePage: (int) page pc: (int) pc dbd: (BOOL) dbd
	mpbuf: (struct mode_parameters *) mpbuf;
{
	int tmp;
	
	if( scsiOpen)
		return  [self modeSenseSCSIPage: page pc: pc dbd: dbd
			mpbuf: mpbuf];
	else
		{
		if([self openSCSI] )
			{
			sprintf(errorString,"Can't open SCSI driver");
			return -1;
			}
		tmp =  [self modeSenseSCSIPage: page pc: pc  dbd: dbd
			mpbuf: mpbuf];
		[self closeSCSI];
		return( tmp);
		}
}

- (int) modeSenseSCSIPage: (int) page pc: (int) pc dbd: (BOOL) dbd
	mpbuf: (struct mode_parameters *) mpbuf;
{	
	struct cdb_6 *cdbp = &sr.sr_cdb.cdb_c6;
	char *bufferPointer;
	int returnValue, i;
	int pageDataLength, pdOffset;
	char *pageBeginning;
	
	 
	if( (bufferPointer = malloc( MODESENSEBUFLENGTH )) == NULL )
		{
		NXRunAlertPanel("Out of Virtual Memory",
			"Inexplicably, this program has run out "
			"of virtual memory. We must quit (and you "
			"should reboot).", "Quit", NULL, NULL );
		[NXApp terminate: self];
		}
	
	bzero( bufferPointer, MODESENSEBUFLENGTH);			
	  
	[SCSI clearCommandBlock: (union cdb *) cdbp];
	cdbp->c6_opcode = C6OP_MODESENSE;
	cdbp->c6_lun    = lun;
	cdbp->c6_lba	= 	
				(((unsigned int) dbd) << 19) + 
				(((unsigned int) pc) << 14) + 
				(((unsigned int) page) << 8);
	cdbp->c6_len 	= 200;
	sr.sr_dma_dir	= SR_DMA_RD;
	sr.sr_addr	= (char *) bufferPointer;	
	sr.sr_dma_max   = 256;
	sr.sr_ioto	= 10;

	returnValue =  [self performSCSIRequest];

	#ifdef DEBUG
		printf("Mode Sense: Return value = %d\n", returnValue);
		printf("LBA  = %08X\n", cdbp->c6_lba);
		printf("Len  = %04X\n", cdbp->c6_len);
		
	#endif
	
	*mpbuf = * ((struct mode_parameters *)  bufferPointer);
	
	mpbuf->blockDescriptorCount =  
		mpbuf->mph.dad.blockDescriptorLength / 8;

	pageDataLength =   mpbuf->mph.dad.modeDataLength - 3 -
			mpbuf->mph.dad.blockDescriptorLength;
	
	pageBeginning = bufferPointer + 4 + 
		mpbuf->mph.dad.blockDescriptorLength;

	#ifdef DEBUG2
		fprintf(stderr,"Page Data Length = %d, "
			"Block Desc Length = %d\n",
			pageDataLength, mpbuf->mph.dad.blockDescriptorLength);
	#endif
	
	
	for( i = 0; i < mpbuf->blockDescriptorCount; i ++ )
		{
		#ifdef DEBUG2
			fprintf(stderr,"Printing block descriptor #%d\n", i);
		#endif
		mpbuf->mpbd[i] = * (struct ModeParameterBlockDescriptor *)
			(bufferPointer + 4 + (8 * i));
		}
			
	if( pageDataLength <= 2)
		{
		mpbuf->pageCount = 0;
		return -1;
		}

	pdOffset = 0;
	
	for( mpbuf->pageCount =0; 
		 pdOffset < pageDataLength && mpbuf->pageCount < MAXPAGES;
		 mpbuf->pageCount ++)
		{
		mpbuf->mpp[mpbuf->pageCount]= * ((union PageFormat *)  
			pageBeginning + pdOffset);
				
		#ifdef DEBUG2
			fprintf(stderr, "Page %d -- len %d (pdOffset %d)\n",
				mpbuf->pageCount,
				 mpbuf->mpp[mpbuf->pageCount].genericPage.
				 	pageHeader.pageLength,
					pdOffset);
		#endif
		
		if( mpbuf->mpp[mpbuf->pageCount].
			genericPage.pageHeader.pageLength == 0)
			break;
			
		pdOffset += mpbuf->mpp[mpbuf->pageCount].genericPage.
			pageHeader.pageLength;
		}
	
	free(bufferPointer);
	return returnValue;
}



- (const char *) identifyDeviceType: (struct inquiry_reply *)  ibuffer
{
	switch( ibuffer->ir_devicetype )
		{
		case DEVTYPE_DISK:
			if( ibuffer->ir_removable == YES)
				return( "Removable-Disk" );
			else
				return( "Hard-Drive" );
		case DEVTYPE_TAPE:														
			return( "Tape-Drive" );
		case DEVTYPE_PRINTER:
			return(  "Printer");
		case DEVTYPE_PROCESSOR:
			return( "Processor" );
		case DEVTYPE_WORM:
			if( ibuffer->ir_removable == YES)
				return( "Removable-Worm" );
			else
				return( "Fixed-Worm" );
		case DEVTYPE_CDROM:
			if( ibuffer->ir_removable == YES)
				return(	"Removable-ReadOnly" );
			else
				return( "Fixed-ReadOnly" );
		case DEVTYPE_SCANNER:
			return( "Scanner" );
		case DEVTYPE_OPTICAL:
			return( "Optical" );
		case DEVTYPE_STILLVIDEO:
			return( "Still-Video" );
		case DEVTYPE_NOTPRESENT:
			return( "No-Device" );
		default:
			NXLogError("Unrecognized device type %02Xh (%d).", ibuffer->ir_devicetype ,  ibuffer->ir_devicetype );
			return( "Not-Recognized" );
		}				
}

//  =====================================================
// 	EXTERNAL LINKS FOR PROGRAMMERS
//  =====================================================
- (int) executeRequest: (struct scsi_req *) sp 
{
	int error;
	sr = *sp;
	error = [self performSCSIRequest];
	*sp = sr;
	return error;
}

- (int) executeBusReset
{
	if (ioctl(fd,SGIOCRST, NULL) < 0) 
		{
		#ifdef DEBUG
			printf("..Error executing ioctl\n");
			printf("errno = %d\n",errno);
		#endif
		return errno;
		}
	return 0;
} 

- (char *) returnError: sender
{
	char *driverStatus, *scsiStatus, *esense;
	
	return( [self returnDriverStatus: &driverStatus
		scsiStatus: &scsiStatus andExtendedSense: &esense] );
}

- (char *) returnDriverStatus: (char **) driverStatus scsiStatus:
	(char **) scsiStatus andExtendedSense: (char **) esense
{
	// we have three error codes
	// io_status -- Driver status
	// status -- scsi status byte returned by target
	// esense -- extended sense data

	static char stringBuffer[512];
	
	
	#ifdef DEBUG
		fprintf( stderr, "Io_Status = %02Xh, Status = %02Xh.\n",	
		sr.sr_io_status, sr.sr_scsi_status);
	#endif
		
		
	switch( sr.sr_io_status )
		{
		case SR_IOST_GOOD:
			*driverStatus = "Command successful";
			break;
		case SR_IOST_SELTO:
			*driverStatus = "Selection timeout";
			break;
		case SR_IOST_CHKSV:
			*driverStatus = 
				"Check target status and extended sense";
			break;
		case SR_IOST_CHKSNV:
			*driverStatus = 
				"Check target status";
			break;
		case SR_IOST_DMAOR:
			*driverStatus = 
				"Target attempted to move more than allocated "
				"amount of data bytes";
			break;
		case SR_IOST_IOTO:
			*driverStatus = "Command timed out";
			break;
		case SR_IOST_BV:
			*driverStatus = "SCSI Bus violation";
			break;
		case SR_IOST_CMDREJ:
			*driverStatus = "Command rejected by driver";
			break;
		case SR_IOST_MEMALL:
			*driverStatus = "Memory allocation failure";
			break;
		case SR_IOST_MEMF:
			*driverStatus = "Memory fault";
			break;
		case SR_IOST_PERM:
			*driverStatus = 
				"Permission failure (not super user)";
			break;
		case SR_IOST_NOPEN:
			*driverStatus = 
				"Device not open";
			break;
		case SR_IOST_TABT:
			*driverStatus = 
				"Target aborted command";
			break;
		case ST_IOST_BADST:
			*driverStatus = 
				"Bad SCSI status byte "
				"(other than check status)";
			break;
		case ST_IOST_INT:
			*driverStatus = "Internal driver error";
			break;
		case SR_IOST_BCOUNT:
			*driverStatus = 
				"Unexpected byte count seen on SCSI bus";
			break;
		case SR_IOST_VOLNA:
			*driverStatus = 
				"Desired volume not available";
			break;
		case SR_IOST_WP:
			*driverStatus = 
				"Media Write Protected";
			break;
		default:
			*driverStatus = "Unrecognizable status";
			break;
		}
	
	switch( sr.sr_scsi_status & STAT_VALIDMASK)
		{	
		case STAT_GOOD:
			if(  sr.sr_io_status == SR_IOST_GOOD )
				*scsiStatus = "GOOD - Command successful";
			else
				*scsiStatus = "Not Applicable "
					"(Command not executed)";
			break;
		case STAT_CHECK:
			*scsiStatus = "CHECK CONDITION - "
				"Abnormal condition occured";
			break;
		case STAT_CONDMET:
			*scsiStatus = "CONDITION MET / GOOD";
			break;
		case STAT_BUSY:
			*scsiStatus = "BUSY - Target Busy";
			break;
		case STAT_INTMGOOD:
			*scsiStatus = "INTERMEDIATE / GOOD";
			break;
		case STAT_INTMCONDMET:
			*scsiStatus = "INTERMEDIATE / CONDITION MET / GOOD";
			break;
		case STAT_RESERVED:
			*scsiStatus = "RESERVATION CONFLICT";
			break;
		case STAT_TERMINATED:
			*scsiStatus = "COMMAND TERMINATED";
			break;
		case STAT_QUEUEFULL:
			*scsiStatus = "QUEUE FULL";
			break;
		default:
			*scsiStatus = "Unrecognizable status";
		}
	
	
	switch( sr.sr_esense.er_sensekey )
		{
		case SENSE_NOSENSE:
			*esense = "No error to report";
			break;
		case SENSE_RECOVERED:
			*esense = "Recovered from error";
			break;
		case SENSE_NOTREADY:
			*esense = "Target not ready";
			break;
		case SENSE_MEDIA:
			*esense = "Media flaw";
			break;
		case SENSE_HARDWARE:
			*esense = "Hardware failure";
			break;
		case SENSE_ILLEGALREQUEST:
			*esense = "Illegal request";
			break;
		case SENSE_UNITATTENTION:
			*esense = "Drive attention";
			break;
		case SENSE_DATAPROTECT:
			*esense = "Drive access protected";
			break;
		case 	SENSE_ABORTEDCOMMAND:
			*esense = "Target aborted command";
			break;
		case 	SENSE_VOLUMEOVERFLOW:
			*esense = "End of media, some data not "
				"transfered";
			break;
		case 	SENSE_MISCOMPARE:
			*esense = "Source/media data mismatch";
			break;
		default:
			*esense = "Unrecognizable sense key";
			break;
		}
		
	if( 	SR_IOST_CHKSV == sr.sr_io_status )
		sprintf( stringBuffer, 
			"\tDriver Status: \t%s\n"
			"\tTarget Status: \t%s\n"
			"\tExtended Sense: \t%s\n",
			*driverStatus,
			*scsiStatus,
			*esense);
	else
		{
		sprintf( stringBuffer, 
			"\tDriver Status: \t%s\n"
			"\tTarget Status:\t%s\n",
			*driverStatus,
			*scsiStatus);
		*esense = NULL;
		}
	
	#ifdef DEBUG
		fprintf(stderr, "SCSI Completion string buf = %s\n",
			stringBuffer);
	#endif 
	return stringBuffer;
}	

@end

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.