package org.jscsi.target.connection.stage.fullfeature;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.jscsi.exception.InternetSCSIException;
import org.jscsi.parser.BasicHeaderSegment;
import org.jscsi.parser.ProtocolDataUnit;
import org.jscsi.parser.scsi.SCSICommandParser;
import org.jscsi.parser.scsi.SCSIResponseParser.ServiceResponse;
import org.jscsi.parser.scsi.SCSIStatus;
import org.jscsi.target.connection.TargetPduFactory;
import org.jscsi.target.connection.phase.TargetFullFeaturePhase;
import org.jscsi.target.scsi.ScsiResponseDataSegment;
import org.jscsi.target.scsi.cdb.Read10Cdb;
import org.jscsi.target.scsi.cdb.Read6Cdb;
import org.jscsi.target.scsi.cdb.ReadCdb;
import org.jscsi.target.scsi.cdb.ScsiOperationCode;
import org.jscsi.target.settings.SettingsException;
import org.jscsi.utils.ByteBufferCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A stage for processing <code>READ (6)</code> and <code>READ (10)</code> SCSI
* commands.
*
* @author Andreas Ergenzinger
*/
public class ReadStage extends ReadOrWriteStage {
private static final Logger LOGGER = LoggerFactory.getLogger(ReadStage.class);
public ReadStage(final TargetFullFeaturePhase targetFullFeaturePhase) {
super(targetFullFeaturePhase);
}
@Override
public void execute(ProtocolDataUnit pdu) throws IOException, InterruptedException, InternetSCSIException,
SettingsException {
try {
doExecute(pdu);
} catch (IOException e) {
// TODO: Send check condition to the initiator, and then when the initiator will request MODE SENSE,
// return a response with Medium Error - unrecovered read error (see http://en.wikipedia.org/wiki/Key_Code_Qualifier)
throw e;
}
}
private void doExecute(ProtocolDataUnit pdu) throws IOException, InterruptedException,
InternetSCSIException, SettingsException {
// get relevant variables ...
// ... from settings
final boolean immediateData = settings.getImmediateData();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("immediateData = " + immediateData);
LOGGER.debug("maxRecvDataSegmentLength = " + settings.getMaxRecvDataSegmentLength());
}
// ... and from the PDU
BasicHeaderSegment bhs = pdu.getBasicHeaderSegment();
SCSICommandParser parser = (SCSICommandParser)bhs.getParser();
final int initiatorTaskTag = bhs.getInitiatorTaskTag();
// get the Read(6) or Read(10) CDB
ReadCdb cdb;
final ScsiOperationCode scsiOpCode = ScsiOperationCode.valueOf(parser.getCDB().get(0));
if (scsiOpCode == ScsiOperationCode.READ_10)// most likely option first
cdb = new Read10Cdb(parser.getCDB());
else if (scsiOpCode == ScsiOperationCode.READ_6)
cdb = new Read6Cdb(parser.getCDB());
else {
// anything else wouldn't be good (programmer error)
// close connection
throw new InternetSCSIException("wrong SCSI Operation Code " + scsiOpCode + " in ReadStage");
}
// check if requested blocks are out of bounds
checkOverAndUnderflow(cdb);
// check illegal field pointers
if (cdb.getIllegalFieldPointers() != null) {
// the command must fail
LOGGER.error("illegal field in Read CDB");
// create and send error PDU and leave stage
final ProtocolDataUnit responsePdu = createFixedFormatErrorPdu(cdb.getIllegalFieldPointers(),// senseKeySpecificData
initiatorTaskTag, parser.getExpectedDataTransferLength());
connection.sendPdu(responsePdu);
return;
}
final int blockSize = session.getStorageModule().getBlockSize();
final int totalTransferLength = blockSize * cdb.getTransferLength();
final long storageOffset = blockSize * cdb.getLogicalBlockAddress();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("cdb.getLogicalBlockAddress() = " + cdb.getLogicalBlockAddress());
LOGGER.debug("blockSize = " + session.getStorageModule().getBlockSize());
LOGGER.debug("totalTransferLength = " + totalTransferLength);
LOGGER.debug("expectedDataSegmentLength = " + parser.getExpectedDataTransferLength());
}
// *** start sending ***
// initialize counters and data segment buffer
int bytesSent = 0;
int dataSequenceNumber = 0;
//byte[] dataSegmentArray = null;
ByteBuffer dataSegment = null;
ProtocolDataUnit responsePdu;
// *** send up to last but one Data-In PDU ***
// (with DataSegmentSize == MaxRecvDataSegmentLength)
if (bytesSent < totalTransferLength - settings.getMaxRecvDataSegmentLength()) {
/*
* Initialize dataSegmentArray and dataSegment with
* MaxRecvDataSegmentLength bytes.
*/
//dataSegment = connection.getDataInBuffer(settings.getMaxRecvDataSegmentLength());
//dataSegment = ByteBuffer.wrap(dataSegmentArray);
}
while (bytesSent < totalTransferLength - settings.getMaxRecvDataSegmentLength()) {
dataSegment = ByteBufferCache.allocate(settings.getMaxRecvDataSegmentLength());
// get data and prepare data segment
session.getStorageModule().read(dataSegment, storageOffset + bytesSent);
// create and send PDU
responsePdu = TargetPduFactory.createDataInPdu(false,// finalFlag,
// not the last
// PDU with
// data payload
// in the
// sequence
false,// acknowledgeFlag, ErrorRecoveryLevel == 0, so we
// never do that
false,// residualOverflowFlag
false,// residualUnderflowFlag
false,// statusFlag
SCSIStatus.GOOD,// status, actually reserved i.e. 0x0
0L,// logicalUnitNumber, reserved
initiatorTaskTag, 0xffffffff,// targetTransferTag
dataSequenceNumber,// dataSequenceNumber
bytesSent,// bufferOffset
0,// residualCount
dataSegment);
connection.sendPdu(responsePdu);
// increment counters
++dataSequenceNumber;
bytesSent += settings.getMaxRecvDataSegmentLength();
}
/*
* If ImmediateData=Yes has been negotiated, then a phase collapse has
* to take place, i.e. the status is sent in the last Data-In PDU.
* Otherwise a separate SCSI Response PDU must follow.
*/
// *** send last Data-In PDU ***
// get data and prepare data segment
final int bytesRemaining = totalTransferLength - bytesSent;
dataSegment = ByteBufferCache.allocate(bytesRemaining);
session.getStorageModule().read(dataSegment, storageOffset + bytesSent);
//dataSegment = ByteBuffer.wrap(dataSegmentArray);
// create and send PDU (with or without status)
responsePdu = TargetPduFactory.createDataInPdu(true,// finalFlag, last
// PDU in the
// sequence with
// data payload
false,// acknowledgeFlag, ErrorRecoveryLevel == 0, so we never
// do that
false,// residualOverflowFlag
false,// residualUnderflowFlag
immediateData,// statusFlag
SCSIStatus.GOOD,// status, or not (reserved if no status)
0L,// logicalUnitNumber, reserved
initiatorTaskTag, 0xffffffff,// targetTransferTag
dataSequenceNumber,// dataSequenceNumber
bytesSent,// bufferOffset
0,// residualCount
dataSegment);
LOGGER.debug("sending last Data-In PDU");
connection.sendPdu(responsePdu);
// send SCSI Response PDU?
if (!immediateData) {
responsePdu = TargetPduFactory.createSCSIResponsePdu(false,// bidirectionalReadResidualOverflow
false,// bidirectionalReadResidualUnderflow
false,// residualOverflow
false,// residualUnderflow
ServiceResponse.COMMAND_COMPLETED_AT_TARGET,// response
SCSIStatus.GOOD,// status
initiatorTaskTag,// initiatorTaskTag
0,// snackTag, reserved
0,// expectedDataSequenceNumber, reserved
0,// bidirectionalReadResidualCount
0,// residualCount
ScsiResponseDataSegment.EMPTY_DATA_SEGMENT);// empty
// ScsiResponseDataSegment
LOGGER.debug("sending SCSI Response PDU");
connection.sendPdu(responsePdu);
}
}
}