ftp.nice.ch/pub/next/tools/scsi/SCSI2_ToolBox.941207.NI.bs.gnutar.gz#/SCSI2_ToolBox/SCSI2_Kit/Source/SCSI.m

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

//
//	SCSI2_Class - provides architecture independendent low-level
//				  SCSI device access.
//
//	Copyright (C) 1994 by Christopher Wolf.
//
//	This library is free software; you can redistribute it and/or
//	modify it under the terms of the GNU Library General Public
//	License as published by the Free Software Foundation; either
//	version 2 of the License, or (at your option) any later version.
//
//	This library is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//	Library General Public License for more details.
//
//	You should have received a copy of the GNU Library General Public
//	License along with this library; if not, write to the Free
//	Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//	Bug-reports, comments and questions should be directed to:
//		Christopher Wolf <chris@alchemy.geo.cornell.edu>
//
//	Portions of this code and documentation are derived from an 
//	earlier work (SCSI Inquirer) Copyright (C) 1990, 1991, 1992 by 
//	Jiro Nakamura and used with permission.
//
//  Portions of this code  are derived from code provided by
//	Andreas Ploeger and used with permission.
//

//
// 	For best results view this file with a tab size of 4 and
//	a display width of 132 columns or wider.
//

//	file version information
#define RCSSM "$Id: SCSI.m,v 0.154 94/12/07 04:04:34 chris Exp $"

//	generic Unix headers
#import <libc.h>
#import <stdio.h>
#import <strings.h>
#import <fcntl.h>
#import <syslog.h>

//	NeXTSTEP headers
#import <appkit/appkit.h>

//	SCSI2 class specific headers 
#import "SCSI.h"
#import "Version.h"
#import "scsi2reg.h"
#import "errio.h"

// string constants
const char RCSsm[]  = RCSSM;
const char RCSsh[]  = RCSSH;
const char RCSsrh[] = RCSSRH;



//	**********************************************************************
//
//	Functions for reading and writing big endian values of
//	various bit sizes on both big (Motorola) and little (Intel) endian
//	architecture machines.
//
//	**********************************************************************


//	----------------------------------------------------------------------
//
//	NAME
//		readBigEndianLongFrom(void *address) 
//
//	PARAMETERS
//		void *address			pointer to MSB of 32 bit big-endian int
//
//	RETURNS
//		(int)					32 bit int value in native byte order
//		
//	EXPLANATION
//		Reads a long-word (4 bytes) stored in big-endian (MSB first) 
//		order starting from the specified address and returns the value
//		as a 32 bit int in native byte order.
//
//	SEE ALSO
//		readBigEndianShort(), readBigEndianValue(),
//		writeBigEndianLong(), writeBigEndianShort(),
//		writeBigEndianValue():
//
//	----------------------------------------------------------------------
	
u_int readBigEndianLong(void *address)
	{
	return readBigEndianValue(address,32);
	}
	

//	----------------------------------------------------------------------
//
//	NAME
//		readBigEndianShortFrom(void *address) 
//
//	PARAMETERS
//		void *address			pointer to MSB of 16 bit big-endian int
//
//	RETURNS
//		(int)					32 bit int value in native byte order
//		
//	EXPLANATION
//		Reads a short-word (2 bytes) stored in big-endian (MSB first) 
//		order starting from the specified address and returns the value
//		as a 32 bit int in native byte order.  
//
//	SEE ALSO
//		readBigEndianLong(), readBigEndianValue(),
//		writeBigEndianLong(), writeBigEndianShort(),
//		writeBigEndianValue()
//
//	----------------------------------------------------------------------
	
u_int readBigEndianShort(void *address)
	{
	return readBigEndianValue(address, 16);	
	}


//	----------------------------------------------------------------------
//
//	NAME
//		readBigEndianValue(void *address, int numBits) 
//
//	PARAMETERS
//		void *address			pointer to MSB of big-endian int
//		int numBits				number of bits in big-endian int (1-32)
//
//	RETURNS
//		(int)					32 bit int value in native byte order
//		
//	EXPLANATION
//		Reads an integer consisting of the specified number of bits
//		stored in big-endian (MSB first) format starting from the
//		specified address.  The number of bits can be from 1-32.
//		The value is returned as a 32 bit int in native byte order.
//
//	SEE ALSO
//		readBigEndianShort(), readBigEndianLong(),
//		writeBigEndianLong(), writeBigEndianShort(),
//		writeBigEndianValue():
//
//	----------------------------------------------------------------------

u_int readBigEndianValue(void *address, int numBits) 
	{
	#ifdef __BIG_ENDIAN__
		return (*((int *)address))>>(32-numBits);
	#else
		return (htonl(*((int *)address)))>>(32-numBits);		
	#endif
	}


//	----------------------------------------------------------------------
//
//	NAME
//		writeBigEndianLong(int value, void *address)
//
//	PARAMETERS
//		int value 				32 bit int value in native byte order
//		void *address			pointer to MSB of 32-bit destination
//		
//	EXPLANATION
//		Writes a value specified as a 32 bit long-word in native byte 
//		order as a 32 bit big-endian value at the specified destination 
//		address.
//
//	SEE ALSO
//		writeBigEndianShort(), writeBigEndianValue(),
//		readBigEndianShort(), readBigEndianLong(), 
//		readBigEndianValue()
//
//	----------------------------------------------------------------------
	
void writeBigEndianLong(int value,  void *address)
	{
	writeBigEndianValue(value, address, 32);
	}
	

//	----------------------------------------------------------------------
//
//	NAME
//		writeBigEndianShort(int value, void *address) 
//
//	PARAMETERS
//		int value 			32 bit int value in native byte order
//		void *address		pointer to MSB of 16-bit destination
//		
//	EXPLANATION
//		Writes a value specified as a 32 bit long-word in native byte 
//		order as a 16 bit big-endian value at the specified destination 
//		address.
//
//	SEE ALSO
//		writeBigEndianLong(), writeBigEndianValue(),
//		readBigEndianShort(), readBigEndianLong(), 
//		readBigEndianValue():
//
//	----------------------------------------------------------------------
	
void writeBigEndianShort(int value, void *address)
	{
	writeBigEndianValue(value, address, 16);
	}
	

//	----------------------------------------------------------------------
//
//	NAME
//		writeBigEndianValue(int value, void *address, int numbits) 
//
//	PARAMETERS
//		int *value 			32 bit int value in native byte order
//		void *address		pointer to MSB of destination
//		int numBits			number of bits for destination value
//		
//	EXPLANATION
//		Writes a value specified as a 32 bit long-word in native byte 
//		order as a big-endian value with the specified number of bits
//		at the specified destination address.  Number of bits specified
//		can be between 1 and 32.
//
//	----------------------------------------------------------------------
	
void writeBigEndianValue(int value, void *address, int numBits)
	{
	#ifdef __BIG_ENDIAN__
		*((int *)address) &= (~(0xFFFF<<(32-numBits)));
		*((int *)address) |= (value<<(32-numBits));	
	#else
		*((int *)address) &= htonl(~(0xFFFF<<(32-numBits)));
		*((int *)address) |= htonl(value<<(32-numBits));
	#endif
	}



@implementation SCSI



//	**********************************************************************
//	
//	Misc. Class (Factory) Methods
//	
//	**********************************************************************


//	----------------------------------------------------------------------
//
//	NAME
//		clearCommandBlock:
//
//	PARAMETERS
//		(cdb *) cdb		pointer to command data block to zero
//
//	EXPLANATION
//		Clears the specifed command data block in preparation for issuing
//		a SCSI command.
//
//	----------------------------------------------------------------------

+ (void) clearCommandBlock: (cdb *) cdb
	{	
	bzero((char *)cdb,12);
	return;
	}



//	**********************************************************************
//
//	Instance methods for archiving.
//
//	**********************************************************************


//	----------------------------------------------------------------------
//
//	NAME
//		read
//
//	RETURNS
//		self
//
//	EXPLANATION
//		Retrieves instance variables associated with this class during
//		an archive read: operation.
//
//	SEE ALSO
//		write:
//	
//	----------------------------------------------------------------------

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


//	----------------------------------------------------------------------
//
//	NAME
//		write
//
//	RETURNS
//		self
//
//	EXPLANATION
//		Preserves instance variables associated with this class during
//		an archive write: operation.
//
//	SEE ALSO
//		read:
//	
//	----------------------------------------------------------------------

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


//	**********************************************************************
//
//	Instance methods for initializing SCSI class and setting/getting
//	miscellaneous parameters.
//
//	**********************************************************************


//	----------------------------------------------------------------------
//
//	NAME
//		init
//
//	RETURNS
//		self					
//		
//	EXPLANATION
//		Initializes a new instance of the SCSI class.
//		
//	----------------------------------------------------------------------

- init
	{		
	target = -1;
	lun = 0;
 	dev_name = "/dev/sg";
	ioctlCode = SGIOCREQ;	
	isOpen = FALSE;
	timeout = 30;
	dprintf = printfToStderr;
	return [super init];
	}


//	----------------------------------------------------------------------
//
//	NAME
//		setTimeout:
//
//	PARAMETERS
//		(int)to				Timeout delay in seconds.
//
//	EXPLANATION
//		Sets the timeout delay for SCSI pre-packaged command methods.
//		Timeout defaults to 30 seconds which should be fine for most
//		commands.
//		
//	SEE ALSO:
//		timeout, setImmediate:
//
//	----------------------------------------------------------------------

- (void)setTimeout: (int)to
	{
	timeout = to;
	}


//	----------------------------------------------------------------------
//
//	NAME
//		timeout
//
//	RETURNS
//		(int)timeout	Current timeout delay in seconds.		
//
//	EXPLANATION
//		Returns the current timeout delay for SCSI pre-packaged command
//		methods.  
//
//	SEE ALSO
//		setTimeout, setImmediate:
//		
//	----------------------------------------------------------------------
	
- (int) timeout
	{
	return timeout;
	}
	

//	----------------------------------------------------------------------
//
//	NAME
//		setImmediate:
//
//	PARAMETERS
//		(BOOL)immediate		Specifies immediate mode on or off.
//
//	EXPLANATION
//		If setImmediate: TRUE is used lengthy commands (such as FORMAT 
//		UNIT) will be executed in immediate mode and return as soon
//		as the device successfully acknowledges the command without 
//		necessarily waiting for the command to complete.  If 
//		setImmediate: FALSE is used all commands will not return until
//		they have been entirely completed by the target device.  The
//		default is immediate command execution.  If immediate execution
//		is disabled you may have to adjust the timeout value (using
//		setTimeout:) since the default 30 second SCSI command timeout
//		may not be sufficient to allow lengthy commands such as a 
//		FORMAT UNIT to complete.
//
//	SEE ALSO
//		setTimeout, immediate:
//		
//	----------------------------------------------------------------------

- (void)setImmediate: (BOOL)immediate
	{
	immed = immediate;
	}


//	----------------------------------------------------------------------
//
//	NAME
//		immediate
//
//	RETURNS
//		(int)immediate		Current immediate execution mode selection.
//
//	EXPLANATION
//		If this method returns true then immediate command execution
//		mode is currently enabled (the default).  If this method returns
//		false immediate mode execution is disabled and you may have to
//		adjust the timeout to allow sufficient time for execution of
//		lengthy commands such as FORMAT UNIT.
//
//	SEE ALSO
//		setTimeout, setImmediate:
//		
//	----------------------------------------------------------------------
	
- (BOOL)immediate
	{
	return immed;
	}
		


//	**********************************************************************
//
//	Instance methods for opening, closing, and setting the target/lun
//	for the SCSI device.
//
//	**********************************************************************

	
//	----------------------------------------------------------------------
//
//	NAME
//		openDevice: ioctlCode: 
//
//	PARAMETERS
//		(const char *) deviceName	SCSI device to open (i.e /dev/mt0)
//		(int) ioctlCode				ioctl request type to use 
//
//	RETURNS
//		(int) 0			0 = success, -1 failure
//
//	EXPLANATION
//		Opens the specified SCSI device.   The ioctlCode value specifies
//		what value will be passed in the request field for ioctl calls 
//		used to issue command requests to this device.  For example, 
//		for generic SCSI devices the request type is SGIOCTLREQ, for 
//		tape devices it's MTIOCSRQ, for disk type devices it's DKIOCREQ. 
//		The SCSI driver should be kept open for as short a time as 
//		possible. Pre-packaged command methods implemented in this class
//		automatically open and close the SCSI driver, if necessary.  If
//		using pre-packaged command methods with a device OTHER than the 
//		generic SCSI device the setDevice:ioctlCode: method must be used
//		to specify the correct device and ioctl code to use.
//
//	SEE ALSO:
//		setDevice:ioctlCode:, openSCSI, closeSCSI
//		
//	----------------------------------------------------------------------

- (int)openDevice:(const char *)deviceName ioctlCode: (int)iCode
	{       
	
	dev_name = NXCopyStringBuffer(deviceName);
  	target = -2;
  	lun = 0;
  	ioctlCode = iCode;
	
	return [self openSCSI];
	
	}


//	----------------------------------------------------------------------
//
//	NAME
//		openTarget: lun: 
//
//	PARAMETERS
//		(int) trg		target to select after opening device	 
//		(int) lun		lun to select after opening device
//
//	RETURNS
//		(int) 0			0 = success, -1 failure
//
//	EXPLANATION
//		Opens the SCSI generic driver and selects the designated
//		target device and LUN.  The SCSI driver should be kept
//		open for as short a time as possible. Pre-packaged command
//		methods implemented in this class automatically open and close
// 		the SCSI driver, if necessary.  (However, if you are going
//		to rely on the auto open/close feature of pre-packaged commands
//		you must use setTarget:lun: to specify the target before issuing
//		the pre-packaged command.)
//
//	SEE ALSO:
//		openSCSI, closeSCSI, setTarget: lun:
//		
//	----------------------------------------------------------------------

- (int) openTarget: (int)trg lun: (int)ln
	{
	
	int result;
	
	ioctlCode = SGIOCREQ;
	dev_name = NXCopyStringBuffer("/dev/sg");

	if (!(result = [self setTarget: trg lun: ln]))
		{
		result = [self openSCSI];
		}
		
	return result;

	}


//	----------------------------------------------------------------------
//
//	NAME
//		openSCSI
//
//	RETURNS
//		(int) 			0 = success, -1 = failure
//
//	EXPLANATION
//		Opens the SCSI driver.  If an alternate driver name has been
//		specified previously with setDriver: (i.e. /dev/mt0) that device
//		will be opened, otherwise the first available generic (sg)
//		device will be opened.  If you are using the generic device you
//		must remember to select a target and lun with setTarget:lun:
//		before issuing any SCSI commands.  The SCSI driver should be kept
//		open for as short a time as possible.  Pre-packaged command 
//		methods implemented in this class automatically	open and close
// 		the SCSI driver.  It is an error to attempt to open any SCSI 
//		device if there is already a SCSI device open. 
//
//	SEE ALSO
//		openAt: lun:, openDevice: ioctlCode: closeSCSI, isOpen
//	
//	----------------------------------------------------------------------

- (int) openSCSI
	{
	
	int result = -1;
	char tmpBuf[20];
	unsigned int tmp;
	
	if (isOpen == TRUE)
		{
		
		sprintf(errorString, "SCSI device already open!");
		dprintf(errorString);
		
		}
	else
		{
			
		if (target == -2)
			{

    		if( (!access(dev_name, R_OK | W_OK))
				&& ((fd = open(dev_name, O_RDWR)) >= 0) ) 
		 		{
				isOpen = TRUE;
				result = 0;
      			}
      		else
				{
				sprintf(errorString, "Could not open SCSI device %s\n", dev_name);
				dprintf(errorString);
				perror("open");
				}

			}
		else
			{

			for(tmp = 0; ; tmp ++)
				{
	
				sprintf(tmpBuf, "%s%1X", dev_name, tmp)	;
		
				if (access(tmpBuf, F_OK))	
					{
					break;
					}
							
				if( (!access(tmpBuf, R_OK | W_OK))
					&& ((fd = open(tmpBuf, O_RDWR)) >= 0) 
					&& (ioctl(fd, SGIOCENAS, NULL) >= 0) ) 
					{
					isOpen = TRUE;
					result = 0;
					break;
					}
					
				}
				
			if (result)
				{
				sprintf(errorString,
						"Could not open SCSI generic driver <%sX>.\n"
						"File descriptor = 0x%X\n",
						tmpBuf, (unsigned int) fd);
				dprintf(errorString);
				perror("open");
				}
			else
				{
				if (result = [self setTarget: target lun: lun])
					{
					[self closeSCSI];
					}
				}
			}
		}

	return result;
	
	}
	
	
//	----------------------------------------------------------------------
//
//	NAME
//		setDevice: ioctlCode: 
//
//	PARAMETERS
//		(const char *) deviceName	SCSI device to open (i.e /dev/mt0)
//		(int) ioctlCode				ioctl request type to use 
//
//	RETURNS
//		(int) 			0 = Success, -1 = Error
//
//	EXPLANATION
//		Selects the indicated device for subsequent SCSI commands.  The
//		ioctlCode value specifies what value will be passed in the
//		request field for ioctl calls used to issue command requests to
//		this device.  For example, for generic SCSI devices the request
//		type is SGIOCTLREQ, for tape type devices it is MTIOCSRQ,
//		for disk type devices it's DKIOCREQ.  The error value returned 
//		by this method is semi-meaningless since we don't know if
//		the selected device can be successfully opened until we actually
//		try to open it through the openSCSI: method.  NOTE: The setDevice:
//		method, unlike the setTarget:lun: method can only be called
//	    BEFORE the SCSI device is opened.
//
//	SEE ALSO
//		openDevice:ioctlCode:
//		
//	----------------------------------------------------------------------
	
- (int) setDevice: (const char *)deviceName ioctlCode: (int)iCode
	{
	
	int result = -1;

	if (isOpen == TRUE)
		{
		sprintf(errorString, "SCSI device already open!");
		dprintf(errorString);
		}
	else
		{
		if  (  (access(deviceName, F_OK))
			&& (!access(deviceName, R_OK | W_OK)) )
			{
			ioctlCode = iCode;
			target = -2;
			lun = 0;
			dev_name = NXCopyStringBuffer(deviceName);
			result = 0;
			}
		}
		
	return result;
	
	}
	

//	----------------------------------------------------------------------
//
//	NAME
//		setTarget: lun: 
//
//	PARAMETERS
//		(int) trg		target to select 	 
//		(int) lun		lun to select 
//
//	RETURNS
//		(int) 			0 = Success, -1 = Error
//
//	SETS
//		isOpen			TRUE if open succeeded, FALSE if failed
//		errorString 	ASCII status message
//		target			selected SCSI target
//		lun				selected SCSI lun
//
//	EXPLANATION
//		Selects the indicated target and lun for subsequent SCSI commands.
//
//	SEE ALSO
//		openTarget: lun:
//		
//	----------------------------------------------------------------------

- (int) setTarget: (int) trg lun: (int) ln
	{
	
	int result = 0;
	
	if ( (trg < 0) || (trg > 7) || (lun < 0) || (lun > 7) )
		{
		
		sprintf(errorString, "Missing or invalid target (%d) or lun (%d) specification!\n", target,lun);
		dprintf(errorString);
		result = -1;

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

		}
				
	return result;

	}

	
//	----------------------------------------------------------------------
//
//	NAME
//		closeSCSI
//
//	RETURNS
//		(int) 0			Close succeeded
//		(int) -1		Error (ASCII error message in errorString)
//
//	INSTANCE VARIABLES EFFECTED
//		isOpen			TRUE if open succeeded, FALSE if failed
//		errorString 	ASCII status message
//
//	EXPLANATION
//		Closes the SCSI generic driver.  The SCSI driver should be kept
//		open for as short a time as possible.  Pre-packaged commands 
//		automatically open and close the SCSI driver.  It is an error
//		to close the SCSI device if it is not open.
//
//	SEE ALSO
//	    openAt: lun:, isOpen
//	
//	----------------------------------------------------------------------

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



//	**********************************************************************
//
//	Methods for reporting status and error information.
//
//	**********************************************************************


//	----------------------------------------------------------------------
//
//	NAME
//		setDprintf:
//
//	PARAMETERS
//		(PrintFunction)pf		Pointer to current error print function.
//
//	EXPLANATION
//
//		This method allows the programmer to specify which PrintFunction
//		will be used to display error and debugging messages from the
//		SCSI class and its sub-classes.  The PrintFunction typedef is
//		defined in SCSI.h.  A PrintFunction is is a function which 
//		accepts printf style parameters (a format string followed by a 
//		parameter list) and displays the formatted output to a particular 
//		place.  The programmer can specify a custom PrintFunction or use 
//		one of the following pre-programmed PrintFunctions included in 
//		errio.c:
//
//		printfToStderr - sends formatted output to stder
//		printfToStdout - sends formatted output to stdou
//		printfToSysLog - sends formatted output to system log
//		printfToNull - a do nothing PrintFunction stub
//
//		The default PrintFunction is printfToStdErr.  A pointer to the 
//		currently selected PrintFunction can be retrieved using the dprintf 
//		method.
//	
//		Note to sub-class implementers:  Internally, the pointer to the 
//		currently selected PrintFunction is stored in the dprintf instance
//		variable of the SCSI class.   In order for SCSI sub-class error
//		messages to be properly redirected via the setDprintf method,
//		sub-classes should always use the function pointer in the dprintf
//		instance variable to display error and debugging messages.  
//		For example:
//			
//		dprintf("Your error message here, value: %d string: %s",1,"Hi!");
//
//	SEE ALSO
//		dprintf:
//	
//	----------------------------------------------------------------------

+ setDprintf: (PrintFunction)pf 
	{ 
	dprintf = pf; 
	return self; 
	}


//	----------------------------------------------------------------------
//
//	NAME
//		dprintf
//
//	RETURNS
//		(PrintFunction)			Pointer to current error print function.
//
//	EXPLANATION
//		This returns the pointer to the current function which should
//		be used for printing and formatting all error messages.  See 
//		the explanation of the setDprintf method for more information. 
//
//	SEE ALSO
//		setDprintf:
//	
//	----------------------------------------------------------------------

+ (PrintFunction)dprintf 
	{ 
	return dprintf; 
	}


//	----------------------------------------------------------------------
//
//	NAME
//		isOpen
//
//	RETURNS
//		(BOOL)			YES = Device Open, NO = Device Closed
//
//	EXPLANATION
//		This returns YES if the SCSI device is open and accessible,
//		NO otherwise.
//
//	SEE ALSO
//		open, closeSCSI, openAt: lun:
//	
//	----------------------------------------------------------------------

- (BOOL) isOpen
	{
	return isOpen;
	}


//	----------------------------------------------------------------------
//
//	NAME
//		fd
//
//	RETURNS
//		(int)			File descriptor or -1
//
//	EXPLANATION
//		This returns the file descriptor of the currently open device
//		or -1 if there is no device open.  Useful for performing
//		ioctls on device.
//
//	SEE ALSO
//		open, closeSCSI, openAt: lun:
//	
//	----------------------------------------------------------------------

- (int)fd 
	{ 
	return isOpen ? fd :-1; 
	}


//	----------------------------------------------------------------------
//
//	NAME
//		errorString
//
//	RETURNS
//		(char *)			Pointer to ASCII error string.
//
//	EXPLANATION
//		This returns the class variable errorString which holds 
//		information (in ASCII format) about the last error. It is the 
//		responsibility of subclasses of SCSI to put the proper 
//		information in errorString when an error occurs. The SCSI 
//		class will not return errors in errorString for you. To access
//		bus level errors use the sr return codes or use returnError 
//		to interpret the sr return codes for you.  
//
//	SEE ALSO
//		statusReq
//	
//	----------------------------------------------------------------------

- (char *) errorString
	{
	return &errorString[0];
	}


//	----------------------------------------------------------------------
//
//	NAME
//		statusReq
//
//	RETURNS
//		(struct scsi_req *)			Pointer to stuct containing error info.
//
//	EXPLANATION
//		This returns the status of the last SCSI request as a 
//		pointer to a structure defined in "scsi2reg.".
//
//	SEE ALSO
//		errorString
//	
//	----------------------------------------------------------------------

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


//	----------------------------------------------------------------------
//
//	NAME
//		returnDriverStatus: scsiStatus: andExtendedSense:
//
//	PARAMETERS
//		(char **)driverStatus		pointer to pointer to char which
//									will be set to point to ASCII string 
//									containing driver status information.
//		(char **)scsiStatus			pointer to pointer to char which 
//									will be set to point to ASCII string
//									containing SCSI status information.
//		(char **)andExtendedSense 	pointer to pointer to char which
//									will be set to point to ASCII string
//									containing extended send information.
//
//	RETURNS
//		(char *)		Pointer to composite string containing all the
//						info in the three individual strings
//						driverStatus, scsiStatus and andExtendedSense,
//						concatenated into one long message.
//
//	EXPLANATION
//		This method places interpreted versions of the sr error codes, 
//		including the extended sense error codes if valid, into the 
//		character strings that were given to it. The format of the 
//		returned strings is not strict and should be only used for 
//		debugging purposes.  The actual error codes are more or less
//		taken from "scsi2reg.h". This method is provided as an
//		easy way to provide debugging information.
//
//	SEE ALSO
//		returnError
//		
//	----------------------------------------------------------------------

- (char *) returnDriverStatus: (char **) driverStatus scsiStatus: (char **) scsiStatus 
andExtendedSense: (char **) esense
	{

	static char stringBuffer[512];
		
	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.esr_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 (TRUE)
		{
		sprintf(stringBuffer,
				"\tBytes Transferred:\t%d of max: %d\n" 
				"\tDriver Status:\t%s\n"
				"\tTarget Status:\t%s\n"
				"\tExtended Sense:\t%s\n",
				sr.sr_dma_xfr,sr.sr_dma_max,
				*driverStatus,
				*scsiStatus,
				*esense);
		}

	return stringBuffer;

	}	


//	----------------------------------------------------------------------
//
//	NAME
//		returnError
//
//	RETURNS
//		(char *)		Pointer to a composite string containing
//						concatenated driverStatus, scsiStatus and
//						andExtendedSense info in one place.
//
//	EXPLANATION
//		This method is just a wrapper for the above defined method
//		returnDriverStatus: scsiStatus: andExtendedSense method
//		for cases where you are only interested in the concatenated
//		error string and don't want it broken down into individual
//		components.
//
//	SEE ALSO
//		returnDriverStatus: scsiStatus: andExtendedSense:
//		
//	----------------------------------------------------------------------

- (char *) returnError: sender
	{

	char *driverStatus, *scsiStatus, *esense;
	
	return [self returnDriverStatus: &driverStatus scsiStatus: &scsiStatus andExtendedSense: &esense];

	}




//	**********************************************************************
//
//	Instance methods for determining which 
//	devices are supported by a sub-class of SCSI class.
//
//	**********************************************************************


//	----------------------------------------------------------------------
//
//	NAME
//		findDevice:
//
//	PARAMETERS
//		(int) trg		Target to start search from 	 
//
//	RETURNS
//		(int) 			Target number of first SCSI device greater than
//						or equal to the specified starting number that
//						the sub-class can handle.  Returns -1 if no
//						appropriate device was able to be found.
//
//	EXPLANATION
//		This method starts at the specified target and sequentially steps
//		through the rest of the targets calling the isDevice: method for 
//		each target.  This method returns the number of the first target 
//		which responds TRUE to isDevice, or -1 if none responds TRUE. 
//		Remember that isDevice is over-ridden by sub-class implementations.
//		This means that for instances of the SCSI super class findDevice:
//		will return the number of the next valid SCSI target of any type; 
// 		for instances of a specific sub-class findDevice: will return the
//	 	number of the next device which that sub-class type recognizes.
//
//	SEE ALSO
//		findDevice, isDevice
//
//	----------------------------------------------------------------------

- (int) findDevice: (int) trg
	{
	
	int result = -1, oldTarg = target, oldLun = lun;
	BOOL wasOpen = isOpen; 
	
	if (wasOpen || ![self openTarget: 0 lun: 0])
		{
		
		for(result = trg; result < 8; result++)
			{
			if( ![self setTarget: result lun: 0] && [self isDevice] )
				{
				break;			
				}
			}
		
		if (result == 8)
			{
			result = -1;
			sprintf(errorString, "No target device found.");
			}

		if (oldTarg != -1)
			{			
			[self setTarget: oldTarg lun: oldLun];			
			}
			
		if (!wasOpen && [self closeSCSI])
			{
			result = -1;
			}	

		}
				
	return(result);

	}


//	----------------------------------------------------------------------
//
//	NAME
//		findDevice
//
//	RETURNS
//		(int)			Target # of first SCSI device that the sub-class
//						can handle.
//
//	EXPLANATION
//		This returns the target number of the first SCSI device that the
//		subclass can handle.  Implemented as a wrap for the method
//		[self findDevice: 0]
//
//	SEE ALSO
//		findDevice, isDevice
//	
//	----------------------------------------------------------------------

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


//	----------------------------------------------------------------------
//
//	NAME
//		isDevice
//
//	RETURNS
//		(BOOL)			YES if sub-class can handle device, NO otherwise. 
//
//	EXPLANATION
//		It is the sub-classes responsibility to override this method. 
//		The method should return YES if the subclass of SCSI can handle
//		the device at the current target, NO otherwise.  The SCSI super-
//		class returns YES as long as there is a device attached, no
//		matter what type it is. 
//
//	SEE ALSO
//		findDevice, findDevice:
//	
//	----------------------------------------------------------------------
	
- (BOOL) isDevice
	{
	
	struct inquiry_reply ibuffer;
	BOOL result;
	PrintFunction oldPf = dprintf;
	
	dprintf = printfToNull;
	result = (![self inquiry: &ibuffer]);
	dprintf = oldPf;
	
	return result;
	
	}
	


//	**********************************************************************
//
//	Instance methods for implementing commonly used SCSI commands.
//
//	**********************************************************************


//	----------------------------------------------------------------------
//
//	NAME
//		inquiry:
//
//	PARAMETERS
//		(struct inquiry_reply *) ibuffer 	Structure used to return 
//											inquiry data.
//
//	RETURNS
//		(int) 								0 = success, -1 = error
//
//	EXPLANATION
//		This implements the mandatory SCSI command ªInquiryº (command 
//		group 0, code 0x12). It returns the inquiry data in 
//		the inquiry_reply structure defined in "scsi2reg.h"
//
//	----------------------------------------------------------------------
	
- (int) inquiry: (struct inquiry_reply *) ibuffer
	{
	
	int result = -1;
	BOOL wasOpen = isOpen;
	struct cdb6 *cdbp = &sr.sr_cdb.cdb6;
		
	bzero((char *)ibuffer,sizeof(*ibuffer));
	
	if (wasOpen || ![self openSCSI])
		{

		[SCSI clearCommandBlock: (cdb *) cdbp];
	
		cdbp->c6_opcode = C6OP_INQUIRY;
		writeBigEndianValue(lun<<21, &cdbp->c6_lunlba_BE24, 24);
		cdbp->c6_len	= 36;

		sr.sr_dma_dir	= SR_DMA_RD;
		sr.sr_addr		= (char *)ibuffer;
		sr.sr_dma_max   = sizeof(struct inquiry_reply);
		sr.sr_ioto		= timeout;

		result = [self performRequest];

		if (!wasOpen && [self closeSCSI])
			{
			result = -1;
			}
		
		}
	
	return(result);		

	}

	
	
//	----------------------------------------------------------------------
//
//	NAME
//		readCapacity 
//
//	PARAMETERS
//		(struct capacity_reply *) rbuffer 	Structure used to return 
//											capacity data.
//
//	RETURNS
//		(int) 								0 = success, -1 = error
//
//	EXPLANATION
//		This implements the mandatory SCSI command ªRead Capacityº 
//		(command group 1, code 0x25) for direct-access devices.  It 
//		returns read capacity data (logical unit capacity and block
//		length) in rbuffer, which is a capacity_reply structure 
//		defined in "SCSI2reg.h"
//
//	----------------------------------------------------------------------

- (int) readCapacity: (struct capacity_reply *) rbuffer
	{
	
	int result = -1;
	BOOL wasOpen = isOpen;
	struct cdb10 *cdbp = &sr.sr_cdb.cdb10;
		
	bzero((char *)rbuffer, sizeof(*rbuffer));
	
	if (wasOpen || ![self openSCSI])
		{

		[SCSI clearCommandBlock: (cdb *) cdbp];
		
		cdbp->c10_opcode = C10OP_READCAPACITY;
		cdbp->c10_lun    = lun;
		
		sr.sr_dma_dir	= SR_DMA_RD;
		sr.sr_addr		= (char *)rbuffer;
		sr.sr_dma_max   = sizeof(*rbuffer);
		sr.sr_ioto		= timeout;
	
		result = [self performRequest];

		if (!wasOpen && [self closeSCSI])
			{
			result = -1;
			}	

		}
		
	return(result);		

	}


//	----------------------------------------------------------------------
//
//	NAME
//		requestSense:
//
//	PARAMETERS
//		(struct esense_reply *) buffer 	Structure used to return 
//										extended sense data.
//
//	RETURNS
//		(int) 							0 = success, -1 = error
//
//	EXPLANATION
//		This implements the mandatory SCSI command ªRequest Senseº 
//		(command group 0, code 0x03).  It returns the sense data in 
//		buffer, which is a esense_reply structure defined in 
//		"scsi2reg".
//
//	----------------------------------------------------------------------

- (int)	requestSense: (struct esense_reply *) ebuffer
	{

	int result = -1;
	BOOL wasOpen = isOpen;
	struct cdb6 *cdbp = &sr.sr_cdb.cdb6;
	
	bzero((char *)ebuffer,sizeof(*ebuffer));
	
	if (wasOpen || ![self openSCSI])
		{
		
		[SCSI clearCommandBlock: (cdb *) cdbp];

		cdbp->c6_opcode = C6OP_REQSENSE;
		cdbp->c6_len	= sizeof(*ebuffer);
		writeBigEndianValue((lun<<21), &cdbp->c6_lunlba_BE24, 24);

		sr.sr_dma_dir	= SR_DMA_RD;
		sr.sr_addr		= (char *)ebuffer;
		sr.sr_dma_max   = sizeof(*ebuffer);
		sr.sr_ioto		= timeout;
	
		result = [self performRequest];

		if (!wasOpen && [self closeSCSI])
			{
			result = -1;
			}	
		
		}
		
	return(result);		

	}


//	----------------------------------------------------------------------
//
//	NAME
//		testUnitReady 
//
//	RETURNS
//		(int) 							0 = unit ready, -1 = error
//
//	EXPLANATION
//		This implements the mandatory SCSI command ªTest Unit Readyº 
//		(command group 0, code 0x00).  Returns 0 if no error (unit ready),
//		non-zero otherwise.
//
//	----------------------------------------------------------------------

- (int)	testUnitReady
	{
	
	int result = -1;
	BOOL wasOpen = isOpen;
	struct cdb6 *cdbp = &sr.sr_cdb.cdb6;
	
	if (wasOpen || ![self openSCSI])
		{
	
		[SCSI clearCommandBlock: (cdb *) cdbp];
	
		cdbp->c6_opcode = C6OP_TESTRDY;
		cdbp->c6_len	= 0;
		writeBigEndianValue((lun<<21), &cdbp->c6_lunlba_BE24, 24);

		sr.sr_dma_dir	= SR_DMA_RD;
		sr.sr_addr		= (char *)0;
		sr.sr_dma_max   = 0;
		sr.sr_ioto		= timeout;
	
		result = [self performRequest];

		if (!wasOpen && [self closeSCSI])
			{
			result = -1;
			}	

		}

	return result;		
	
	}


//	----------------------------------------------------------------------
//
//	NAME
//		receiveDiagnostic: size:
//
//	PARAMETERS
//		(char *) dbuf 		Pre-allocated buffer used to return data.
//		(int) size			Length of diagnostic data byffer.
//
//	RETURNS
//		(int) 							0 = success, -1 = error
//
//	EXPLANATION
//		This executes the SCSI-2  ªReceive Diagnosticº command (command 
//		group 0, code 0x1C).  Allocation length of the buffer must be 
//		placed into alloc and a pointer to an allocated buffer space must
//		be passed in buffer.  It is the subclass/developer's 
//		responsibility to parse the diagnostic information. 
//
//	SEE ALSO
//		sendDiagnostic:size:st:dol:uol:
//
//	----------------------------------------------------------------------

- (int)	receiveDiagnostic: (char *)dbuf size: (u_char)size
	{

	int result = -1;
	BOOL wasOpen = isOpen;
	struct cdb6 *cdbp = &sr.sr_cdb.cdb6;

	bzero((char *)dbuf,size);
	
	if (wasOpen || ![self openSCSI])
		{
	
		[SCSI clearCommandBlock: (cdb *) cdbp];
	
		cdbp->c6_opcode = C6OP_RECEIVEDIAG;
		cdbp->c6_len	= size;
		writeBigEndianValue((lun<<21), &cdbp->c6_lunlba_BE24, 24);

		sr.sr_dma_dir	= SR_DMA_RD;
		sr.sr_addr		= (char *)dbuf;
		sr.sr_dma_max   = size;
		sr.sr_ioto		= timeout;
	
		result =  [self performRequest];

		if (!wasOpen && [self closeSCSI])
			{
			result = -1;
			}	

		}
		
	return result;

	}


//	----------------------------------------------------------------------
//
//	NAME
//		sendDiagnostic:size:pf:st:dol:uol:
//
//	PARAMETERS
//		(BOOL) pf				Page format 
//		(BOOL) st				Self test
//		(BOOL) dol				Device off line
//		(BOOL) uol				Unit off line
//		(u_char) size			Parameter list length
//		(char *) dbuf			Parameter buffer
//
//	RETURNS
//		(int) 					0 = success, -1 = error
//
//	EXPLANATION
//		This executes the SCSI-2  ªSend Diagnosticº command (command group 0,  
//		code 0x1D).  See SCSI-2 Spec for more complete description of
//		diagnostic parameters.
//
//	SEE ALSO
//	 	receiveDiagnostic:size:
//
//	----------------------------------------------------------------------

- (int)	sendDiagnostic: (char *)dbuf size: (u_char)size pf: (BOOL)pf st: (BOOL)st  dol: (BOOL)dol uol: (BOOL)uol
	{

	int result = -1;
	BOOL wasOpen = isOpen;
	struct cdb6_send_diagnostic *cdbp = &sr.sr_cdb.cdb6_send_diagnostic;	

	if (wasOpen || ![self openSCSI])
		{
						
		[SCSI clearCommandBlock: (cdb *) cdbp];
	
		cdbp->sd_opcode 	= C6OP_SENDDIAG;
		cdbp->sd_lun 		= lun;
		cdbp->sd_pf			= pf;
		cdbp->sd_selftest 	= st;
		cdbp->sd_devofl		= dol;
		cdbp->sd_unitofl	= uol;
		
		sr.sr_dma_dir	= SR_DMA_WR;
		sr.sr_addr		= dbuf;	
		sr.sr_dma_max   = size;
		sr.sr_ioto		= timeout;
		
		result = [self performRequest];

		if (!wasOpen && [self closeSCSI])
			{
			result = -1;
			}	
	
		}
		
	return result;

	}


//	----------------------------------------------------------------------
//
//	NAME
//		modeSenseRaw:size:page:pc:dbd: 
//
//	PARAMETERS
//		(mode_parameter_list *) mpl		pointer to data buffer 
//		(u_char) size					size of buffer  		
//		(u_char) page					page type		
//		(u_char) pc						page control code
//		(BOOL) dbd						disable block descriptor
//
//	RETURNS
//		(int) 							0 = success, -1 = error
//
//	EXPLANATION
//		This implements the mandatory SCSI command ªMode Senseº (code 
//		0x1A).  The complete mode parameter list structure including 
//		headers and block descriptors (if dbd = FALSE) is returned in 
//		the designated pre-allocated buffer.  It is up to the caller to 
//		interpret this returned data according to the structures defined
//		in scsi2reg.h and the SCSI 2 spec.  If you would prefer that 
//		JUST the requested page data be returned, without header or
//		block descriptor information, use the modeSenseCooked:size:page:pc:
//		method instead.
//
//	SEE ALSO
//		modeSenseCooked:size:page:pc:,modeSelectRaw:size:save:
//
//	----------------------------------------------------------------------

- (int) modeSenseRaw: (mode_parameter_list *)mpl size: (u_char)size page: (u_char)page pc: (u_char)pc dbd: (BOOL)dbd
	{	

	struct cdb6_mode_sense *cdbp = &sr.sr_cdb.cdb6_mode_sense;
	int result = -1;
	BOOL wasOpen = isOpen;

	bzero((char *)mpl,size);
	
	if (wasOpen || ![self openSCSI])
		{
			  	
		[SCSI clearCommandBlock: (cdb *)cdbp];
		
		cdbp->ms_opcode 			= C6OP_MODESENSE;
		cdbp->ms_lun				= lun;
		cdbp->ms_allocationlength	= size;
		cdbp->ms_dbd				= dbd;
		cdbp->ms_pagecode			= page;
		cdbp->ms_pagecontrol		= pc;		
	
		sr.sr_dma_dir	= SR_DMA_RD;
		sr.sr_addr		= (char *)mpl;	
		sr.sr_dma_max   = size;
		sr.sr_ioto		= timeout;
	
		result =  [self performRequest];
	
		if (!wasOpen && [self closeSCSI])
			{
			result = -1;
			}	
	
		}
		
	return result;

	}


//	----------------------------------------------------------------------
//
//	NAME
//	 	modeSelectRaw:size:save: 
//
//	PARAMETERS
//		(mode_parameter_list *) mpl		pointer to mode parameter list 
//		(u_char) size					size of mode parameter list
//		(BOOL) save						TRUE = save parameters
//
//	RETURNS
//		(int) 							0 = success, -1 = error
//
//	EXPLANATION
//		This implements the SCSI command ªMode Selectº.  The data
//		for the mode select operation is passed in the mode parameter
//		list structure.  If the save parameter is specified TRUE
//		the save parameters option will be used for the Mode Select
//		command and the data pages will be stored to non-volatile
//		device memory, if the target device supports this.  Remember
//		to set all reserved fields to NULL and to supply only valid
//		parameters in the mode parameter list pages or this call will
//		probably fail.  Be CAREFUL!
//
//	SEE ALSO
//		modeSenseCooked:size:page:pc:, modeSenseRaw:size:page:pc:dbd: 
//
//	----------------------------------------------------------------------

- (int) modeSelectRaw: (mode_parameter_list *)mpl size: (u_char)size save: (BOOL)save
	{

	struct cdb6_mode_select *cdbp = &sr.sr_cdb.cdb6_mode_select;
	int result = -1;
	BOOL wasOpen = isOpen;

	if (wasOpen || ![self openSCSI])
		{
			  	
		[SCSI clearCommandBlock: (cdb *)cdbp];
		
		cdbp->msl_opcode 	= C6OP_MODESELECT;
		cdbp->msl_lun		= lun;
		cdbp->msl_pf		= 1;
		cdbp->msl_pll		= size;
		cdbp->msl_sp		= save;

		sr.sr_dma_dir	= SR_DMA_WR;
		sr.sr_addr		= (char *)mpl;	
		sr.sr_dma_max   = size;
		sr.sr_ioto		= timeout;
	
		result =  [self performRequest];
	
		if (!wasOpen && [self closeSCSI])
			{
			result = -1;
			}
				
		}
		
  	return result;

	}


//	----------------------------------------------------------------------
//
//	NAME
//		modeSenseCooked:size:page:pc: 
//
//	PARAMETERS
//		(mode_page *) mpdest	pointer to pre-allocated page bugger
//		(u_char) size			page size 		
//		(u_char) page			page type		
//		(u_char) pc				page control code
//
//	RETURNS
//		(int) 					0 = success, -1 = error
//
//	EXPLANATION
//		This is a wrapper for the modeSenseRaw:size:page:pc:dbd: method
//		which returns ONLY a single requested page in the provided
//		buffer stripped of all the mode parameter list header and
//		block descriptor information.  Simpler to decode than the 
//		information returned by modeSenseRaw:size:page:pc:dbd: but not
//		as flexible.
//
//	SEE ALSO
//		modeSenseRaw:size:page:pc:dbd, modeSelectRaw:size:save:
//
//	----------------------------------------------------------------------

- (int) modeSenseCooked: (mode_page *)mpdest size: (u_char)size page: (u_char)page pc: (u_char)pc
	{
	
	mode_parameter_list *mpl;
	mode_page *mpsrc;
	int result = -1;
	
	bzero((char *)mpdest,size);
	
	if (mpl = malloc(255))
		{
		if (![self modeSenseRaw: mpl size: 200 page: page pc: pc dbd: FALSE])
			{
			mpsrc = (mode_page *)(((char *)mpl)+4+mpl->mpl_bdlength);
			if (mpsrc->ph.ph_pagelength < size)
				{
				size = mpsrc->ph.ph_pagelength;
				}
			bcopy((char *)mpsrc,(char *)mpdest,size);
			result = 0;
			}
		free(mpl);
		}
		
	return result;
	
	};
	


//	**********************************************************************
//
//	Instance methods for low-level SCSI access.
//
//	**********************************************************************


//	----------------------------------------------------------------------
//
//	NAME
//		executeRequest
//
//	RETURNS
//		(int)				0 if request succeeded,
//							errno if request failed.
//
//	EXPLANATION
//		This calls the generic SCSI driver with the parameters as set 
//		in the structure sp. This is a low level method which should only
//		be called by other objects which wish to make SCSI driver 
//		command requests. Subclasses of SCSI should use the 
//		performSCSIRequest method instead. Error codes, if any, will be 
//		returned in the scsi_req structure in the predetermined places 
//		(see "scsi2reg.h" for the structure of scsi_req).  This method
//		does not open or close the SCSI device for the caller.
//	
//	----------------------------------------------------------------------

- (int) executeRequest: (struct scsi_req *) sp 
	{

	int error;
	sr = *sp;
	error = [self performRequest];
	*sp = sr;
	return error;

	}


//	----------------------------------------------------------------------
//
//	NAME
//		executeBusReset
//
//	RETURNS
//		(int)				0 if request succeeded,
//							errno if ioctl call failed.
//
//	EXPLANATION
//		This performs a software RESET of the SCSI bus. It can only be
//		performed by someone running as the superuser and should be 
//		used cautiously. See the sg (4) manual page for more information.
//	
//	----------------------------------------------------------------------

- (int) executeBusReset
	{

	if (ioctl(fd,SGIOCRST,NULL) < 0) 
		{
		return errno;
		}
	return 0;

	} 



//	**********************************************************************
//
//	Internal (private!) Instance Methods
//	!!! ONLY SUBCLASSES SHOULD CALL THESE PRIVATE METHODS !!!
//
//	**********************************************************************


//	----------------------------------------------------------------------
//
//	NAME
//		performRequest
//
//	RETURNS
//		(int)				0 if request succeeded,
//							errno if ioctl call failed,
//							sr.sr_io_status if request returned an error.
//
//	EXPLANATION
//		This calls the generic SCSI driver with the parameters as set 
//		in instance variable sr. This is a private method which should 
//		only be called by subclasses of SCSI which wish to make SCSI 
//		driver command requests.  This method does NOT open or close
//		the SCSI device for the caller.
//	
//	----------------------------------------------------------------------
	
- (int) performRequest
	{

	if (ioctl(fd, ioctlCode, &sr) < 0) 
		{
		dprintf("IOctl failed!\nerrno = %d\n",errno);
		return errno;
		}
	
	if(sr.sr_io_status || sr.sr_scsi_status) 
		{
		dprintf("IOReq failed!\nsr_io_status = %d, sr_scsi_status = %d, dma_xfer = %d, dma_max = %d\n%s\n",
				sr.sr_io_status,sr.sr_scsi_status,sr.sr_dma_xfr,sr.sr_dma_max,[self returnError: self]);
		return sr.sr_io_status;
		}
			
	return 0;

	} 

	
@end

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