View Javadoc

1   package org.jscsi.target.connection.stage.fullfeature;
2   
3   
4   import static org.jscsi.target.storage.IStorageModule.VIRTUAL_BLOCK_SIZE;
5   
6   import java.io.IOException;
7   import java.security.DigestException;
8   
9   import org.jscsi.exception.InternetSCSIException;
10  import org.jscsi.parser.AbstractMessageParser;
11  import org.jscsi.parser.BasicHeaderSegment;
12  import org.jscsi.parser.ProtocolDataUnit;
13  import org.jscsi.parser.data.DataOutParser;
14  import org.jscsi.parser.nop.NOPOutParser;
15  import org.jscsi.parser.scsi.SCSICommandParser;
16  import org.jscsi.parser.scsi.SCSIResponseParser;
17  import org.jscsi.parser.scsi.SCSIStatus;
18  import org.jscsi.target.TargetServer;
19  import org.jscsi.target.connection.TargetPduFactory;
20  import org.jscsi.target.connection.phase.TargetFullFeaturePhase;
21  import org.jscsi.target.scsi.ScsiResponseDataSegment;
22  import org.jscsi.target.scsi.cdb.ScsiOperationCode;
23  import org.jscsi.target.scsi.cdb.Write10Cdb;
24  import org.jscsi.target.scsi.cdb.Write6Cdb;
25  import org.jscsi.target.scsi.cdb.WriteCdb;
26  import org.jscsi.target.settings.SettingsException;
27  import org.jscsi.target.util.Debug;
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  
31  
32  /**
33   * A stage for processing <code>WRITE (6)</code> and <code>WRITE (10)</code> SCSI commands.
34   * 
35   * @author Andreas Ergenzinger
36   */
37  public final class WriteStage extends ReadOrWriteStage {
38  
39      private static final Logger LOGGER = LoggerFactory.getLogger(WriteStage.class);
40  
41      /**
42       * The <code>DataSN</code> value the next Data-Out PDU must carry.
43       */
44      private int expectedDataSequenceNumber = 0;
45  
46      public WriteStage (TargetFullFeaturePhase targetFullFeaturePhase) {
47          super(targetFullFeaturePhase);
48      }
49  
50      /**
51       * Is used for checking if the PDUs received in a Data-Out sequence actually are Data-Out PDU and if the PDUs have
52       * been received in order.
53       * 
54       * @param parser the {@link AbstractMessageParser} subclass instance retrieved from the {@link ProtocolDataUnit}'s
55       *            {@link BasicHeaderSegment}
56       * @throws InternetSCSIException if an unexpected PDU has been received
57       */
58      private void checkDataOutParser (final AbstractMessageParser parser) throws InternetSCSIException {
59          if (parser instanceof DataOutParser) {
60              final DataOutParser p = (DataOutParser) parser;
61              if (p.getDataSequenceNumber() != expectedDataSequenceNumber++) { throw new InternetSCSIException("received erroneous PDU in data-out sequence, expected " + (expectedDataSequenceNumber - 1)); }
62          } else if (parser instanceof NOPOutParser || parser instanceof SCSICommandParser) {
63  
64          } else {
65              if (parser != null) {
66                  throw new InternetSCSIException("received erroneous PDU in data-out sequence, " + parser.getClass().getName());
67              } else {
68                  throw new InternetSCSIException("received erroneous PDU in data-out sequence, parser is null");
69              }
70          }
71  
72      }
73  
74      @Override
75      public void execute (ProtocolDataUnit pdu) throws IOException , DigestException , InterruptedException , InternetSCSIException , SettingsException {
76  
77          if (LOGGER.isDebugEnabled()) LOGGER.debug("Entering WRITE STAGE");
78  
79          // get relevant values from settings
80          final boolean immediateData = settings.getImmediateData();
81          final boolean initialR2T = settings.getInitialR2T();
82          final int firstBurstLength = settings.getFirstBurstLength();
83          final int maxBurstLength = settings.getMaxBurstLength();
84  
85          if (LOGGER.isDebugEnabled()) {
86              LOGGER.debug("immediateData = " + immediateData);
87              LOGGER.debug("initialR2T = " + initialR2T);
88          }
89  
90          // get relevant values from PDU/CDB
91          BasicHeaderSegment bhs = pdu.getBasicHeaderSegment();
92          SCSICommandParser parser = (SCSICommandParser) bhs.getParser();
93          final int initiatorTaskTag = bhs.getInitiatorTaskTag();
94          WriteCdb cdb;
95          final ScsiOperationCode scsiOpCode = ScsiOperationCode.valueOf(parser.getCDB().get(0));
96          if (scsiOpCode == ScsiOperationCode.WRITE_10)
97              cdb = new Write10Cdb(parser.getCDB());
98          else if (scsiOpCode == ScsiOperationCode.WRITE_6)
99              cdb = new Write6Cdb(parser.getCDB());
100         else {
101             // anything else wouldn't be good (programmer error)
102             // close connection
103             throw new InternetSCSIException("wrong SCSI Operation Code " + scsiOpCode + " in WriteStage");
104         }
105         final int transferLength = cdb.getTransferLength();
106         final long logicalBlockAddress = cdb.getLogicalBlockAddress();
107 
108         // transform to from block units to byte units
109         final int transferLengthInBytes = transferLength * VIRTUAL_BLOCK_SIZE;
110         long storageIndex = logicalBlockAddress * VIRTUAL_BLOCK_SIZE;
111 
112         // check if requested blocks are out of bounds
113         // (might add FPSKSD to the CDB's list to be detected in the next step)
114         checkOverAndUnderflow(cdb);
115 
116         if (cdb.getIllegalFieldPointers() != null) {
117             /*
118              * CDB is invalid, inform initiator by closing the connection. Sending an error status SCSI Response PDU
119              * will not work reliably, since the initiator may not be expecting a response so soon. Also, if the
120              * WriteStage is simply left early (without closing the connection), the initiator may send additional
121              * unsolicited Data-Out PDUs, which the jSCSI Target is currently unable to ignore or process properly.
122              */
123             LOGGER.debug("illegal field in Write CDB");
124             LOGGER.debug("CDB:\n" + Debug.byteBufferToString(parser.getCDB()));
125             
126             // Not necessarily close the connection
127 
128             // create and send error PDU and leave stage
129             final ProtocolDataUnit responsePdu = createFixedFormatErrorPdu(cdb.getIllegalFieldPointers(),// senseKeySpecificData
130                     initiatorTaskTag, parser.getExpectedDataTransferLength());
131             connection.sendPdu(responsePdu);
132             return;
133         }
134 
135         // *** start receiving data (or process what has already been sent) ***
136         int bytesReceived = 0;
137 
138         // *** receive immediate data ***
139         if (immediateData && bhs.getDataSegmentLength() > 0) {
140             final byte[] immediateDataArray = pdu.getDataSegment().array();
141 
142             session.getStorageModule().write(immediateDataArray, storageIndex);
143             bytesReceived = immediateDataArray.length;
144 
145             if (LOGGER.isDebugEnabled()) LOGGER.debug("wrote " + immediateDataArray.length + "bytes as immediate data");
146         }
147 
148         // *** receive unsolicited data ***
149         if (!initialR2T && !bhs.isFinalFlag()) {
150 
151             if (LOGGER.isDebugEnabled()) LOGGER.debug("receiving unsolicited data");
152 
153             boolean firstBurstOver = false;
154             while (!firstBurstOver && bytesReceived <= firstBurstLength) {
155 
156                 // receive and check PDU
157                 pdu = connection.receivePdu();
158                 bhs = pdu.getBasicHeaderSegment();
159 
160                 checkDataOutParser(bhs.getParser());
161 
162                 final DataOutParser dataOutParser = (DataOutParser) bhs.getParser();
163 
164                 session.getStorageModule().write(pdu.getDataSegment().array(), storageIndex + dataOutParser.getBufferOffset());
165                 ;
166                 bytesReceived += bhs.getDataSegmentLength();
167 
168                 if (bhs.isFinalFlag()) firstBurstOver = true;
169             }
170         }
171 
172         // *** receive solicited data ***
173         if (bytesReceived < transferLengthInBytes) {
174             if (LOGGER.isDebugEnabled()) LOGGER.debug(bytesReceived + "<" + transferLengthInBytes);
175 
176             int readyToTransferSequenceNumber = 0;
177             int desiredDataTransferLength;
178 
179             while (bytesReceived < transferLengthInBytes) {
180 
181                 desiredDataTransferLength = Math.min(maxBurstLength, transferLengthInBytes - bytesReceived);
182 
183                 // send R2T
184                 pdu = TargetPduFactory.createReadyToTransferPdu(0,// logicalUnitNumber
185                         initiatorTaskTag, TargetServer.getNextTargetTransferTag(),// targetTransferTag
186                         readyToTransferSequenceNumber++, bytesReceived,// bufferOffset
187                         desiredDataTransferLength);
188 
189                 connection.sendPdu(pdu);
190 
191                 // receive DataOut PDUs
192                 expectedDataSequenceNumber = 0;// reset sequence counter//FIXME
193                                                // fix in jSCSI Initiator
194                 boolean solicitedDataCycleOver = false;
195                 int bytesReceivedThisCycle = 0;
196                 while (!solicitedDataCycleOver) {
197 
198                     // receive and check PDU
199                     pdu = connection.receivePdu();
200                     bhs = pdu.getBasicHeaderSegment();
201                     checkDataOutParser(bhs.getParser());
202 
203                     if (bhs.getParser() instanceof NOPOutParser) {
204 
205                         /* send SCSI Response PDU */
206                         pdu = TargetPduFactory.createSCSIResponsePdu(false,// bidirectionalReadResidualOverflow
207                                 false,// bidirectionalReadResidualUnderflow
208                                 false,// residualOverflow
209                                 false,// residualUnderflow
210                                 SCSIResponseParser.ServiceResponse.COMMAND_COMPLETED_AT_TARGET,// response
211                                 SCSIStatus.GOOD,// status
212                                 initiatorTaskTag, 0,// snackTag
213                                 0,// (ExpDataSN or) Reserved
214                                 0,// bidirectionalReadResidualCount
215                                 0,// residualCount
216                                 ScsiResponseDataSegment.EMPTY_DATA_SEGMENT);// dataSegment
217 
218                         connection.sendPdu(pdu);
219                         return;
220                     } else if (bhs.getParser() instanceof DataOutParser) {
221                         final DataOutParser dataOutParser = (DataOutParser) bhs.getParser();
222 
223                         session.getStorageModule().write(pdu.getDataSegment().array(), storageIndex + dataOutParser.getBufferOffset());
224 
225                         bytesReceivedThisCycle += bhs.getDataSegmentLength();
226 
227                         /*
228                          * Checking the final flag should be enough, but is not, when dealing with the jSCSI Initiator.
229                          * This is also one of the reasons, why the contents of this while loop, though very similar to
230                          * what is happening during the receiving of the unsolicited data PDU sequence, has not been put
231                          * into a dedicated method.
232                          */
233                         if (bhs.isFinalFlag() || bytesReceivedThisCycle >= desiredDataTransferLength) solicitedDataCycleOver = true;
234                     }
235                 }
236                 bytesReceived += bytesReceivedThisCycle;
237             }
238         }
239 
240         /* send SCSI Response PDU */
241         pdu = TargetPduFactory.createSCSIResponsePdu(false,// bidirectionalReadResidualOverflow
242                 false,// bidirectionalReadResidualUnderflow
243                 false,// residualOverflow
244                 false,// residualUnderflow
245                 SCSIResponseParser.ServiceResponse.COMMAND_COMPLETED_AT_TARGET,// response
246                 SCSIStatus.GOOD,// status
247                 initiatorTaskTag, 0,// snackTag
248                 0,// (ExpDataSN or) Reserved
249                 0,// bidirectionalReadResidualCount
250                 0,// residualCount
251                 ScsiResponseDataSegment.EMPTY_DATA_SEGMENT);// dataSegment
252 
253         connection.sendPdu(pdu);
254     }
255 }