[PATCH] cifs: Handle multiple response transact2 part 1 of 2

Signed-off-by: Steve French (sfrench@us.ibm.com)
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
index 419f145..a8d592b 100644
--- a/fs/cifs/connect.c
+++ b/fs/cifs/connect.c
@@ -116,7 +116,7 @@
 	spin_unlock(&GlobalMid_Lock);
 	server->maxBuf = 0;
 
-	cFYI(1, ("Reconnecting tcp session "));
+	cFYI(1, ("Reconnecting tcp session"));
 
 	/* before reconnecting the tcp session, mark the smb session (uid)
 		and the tid bad so they are not used until reconnected */
@@ -194,6 +194,121 @@
 	return rc;
 }
 
+/* 
+	return codes:
+		0 	not a transact2, or all data present
+		>0 	transact2 with that much data missing
+		-EINVAL = invalid transact2
+
+ */
+static int check2ndT2(struct smb_hdr * pSMB, unsigned int maxBufSize)
+{
+	struct smb_t2_rsp * pSMBt;
+        int total_data_size;
+	int data_in_this_rsp;
+	int remaining;
+
+	if(pSMB->Command != SMB_COM_TRANSACTION2)
+		return 0;
+
+        /* check for plausible wct, bcc and t2 data and parm sizes */
+        /* check for parm and data offset going beyond end of smb */
+	if(pSMB->WordCount != 10) { /* coalesce_t2 depends on this */
+		cFYI(1,("invalid transact2 word count"));
+		return -EINVAL;
+	}
+
+	pSMBt = (struct smb_t2_rsp *)pSMB;
+
+	total_data_size = le16_to_cpu(pSMBt->t2_rsp.TotalDataCount);
+	data_in_this_rsp = le16_to_cpu(pSMBt->t2_rsp.DataCount);
+
+	remaining = total_data_size - data_in_this_rsp;
+
+	if(remaining == 0)
+		return 0;
+	else if(remaining < 0) {
+		cFYI(1,("total data %d smaller than data in frame %d",
+			total_data_size, data_in_this_rsp));
+		return -EINVAL;
+	} else {
+		cFYI(1,("missing %d bytes from transact2, check next response",
+			remaining));
+		if(total_data_size > maxBufSize) {
+			cERROR(1,("TotalDataSize %d is over maximum buffer %d",
+				total_data_size,maxBufSize));
+			return -EINVAL; 
+		}
+		return remaining;
+	}
+}
+
+static int coalesce_t2(struct smb_hdr * psecond, struct smb_hdr *pTargetSMB)
+{
+	struct smb_t2_rsp *pSMB2 = (struct smb_t2_rsp *)psecond;
+	struct smb_t2_rsp *pSMBt  = (struct smb_t2_rsp *)pTargetSMB;
+	int total_data_size;
+	int total_in_buf;
+	int remaining;
+	int total_in_buf2;
+	char * data_area_of_target;
+	char * data_area_of_buf2;
+	__u16 byte_count;
+
+	total_data_size = le16_to_cpu(pSMBt->t2_rsp.TotalDataCount);
+
+	if(total_data_size != le16_to_cpu(pSMB2->t2_rsp.TotalDataCount)) {
+		cFYI(1,("total data sizes of primary and secondary t2 differ"));
+	}
+
+	total_in_buf = le16_to_cpu(pSMBt->t2_rsp.DataCount);
+
+	remaining = total_data_size - total_in_buf;
+	
+	if(remaining < 0)
+		return -EINVAL;
+
+	if(remaining == 0) /* nothing to do, ignore */
+		return 0;
+	
+	total_in_buf2 = le16_to_cpu(pSMB2->t2_rsp.DataCount);
+	if(remaining < total_in_buf2) {
+		cFYI(1,("transact2 2nd response contains too much data"));
+	}
+
+	/* find end of first SMB data area */
+	data_area_of_target = (char *)&pSMBt->hdr.Protocol + 
+				le16_to_cpu(pSMBt->t2_rsp.DataOffset);
+	/* validate target area */
+
+	data_area_of_buf2 = (char *) &pSMB2->hdr.Protocol +
+                                        le16_to_cpu(pSMB2->t2_rsp.DataOffset);
+
+	data_area_of_target += total_in_buf;
+
+	/* copy second buffer into end of first buffer */
+	memcpy(data_area_of_target,data_area_of_buf2,total_in_buf2);
+	total_in_buf += total_in_buf2;
+	pSMBt->t2_rsp.DataCount = cpu_to_le16(total_in_buf);
+	byte_count = le16_to_cpu(BCC_LE(pTargetSMB));
+	byte_count += total_in_buf2;
+	BCC_LE(pTargetSMB) = cpu_to_le16(byte_count);
+
+	byte_count = be32_to_cpu(pTargetSMB->smb_buf_length);
+	byte_count += total_in_buf2;
+
+	/* BB also add check that we are not beyond maximum buffer size */
+		
+	pTargetSMB->smb_buf_length = cpu_to_be32(byte_count);
+
+	if(remaining == total_in_buf2) {
+		cFYI(1,("found the last secondary response"));
+		return 0; /* we are done */
+	} else /* more responses to go */
+		return 1;
+
+}
+
 static int
 cifs_demultiplex_thread(struct TCP_Server_Info *server)
 {
@@ -211,6 +326,8 @@
 	struct mid_q_entry *mid_entry;
 	char *temp;
 	int isLargeBuf = FALSE;
+	int isMultiRsp;
+	int reconnect;
 
 	daemonize("cifsd");
 	allow_signal(SIGKILL);
@@ -254,6 +371,7 @@
 			memset(smallbuf, 0, sizeof (struct smb_hdr));
 
 		isLargeBuf = FALSE;
+		isMultiRsp = FALSE;
 		smb_buffer = smallbuf;
 		iov.iov_base = smb_buffer;
 		iov.iov_len = 4;
@@ -311,13 +429,15 @@
 
 		temp = (char *) smb_buffer;
 		if (temp[0] == (char) RFC1002_SESSION_KEEP_ALIVE) {
-			cFYI(0,("Received 4 byte keep alive packet"));
+			continue; 
 		} else if (temp[0] == (char)RFC1002_POSITIVE_SESSION_RESPONSE) {
-				cFYI(1,("Good RFC 1002 session rsp"));
+			cFYI(1,("Good RFC 1002 session rsp"));
+			continue;
 		} else if (temp[0] == (char)RFC1002_NEGATIVE_SESSION_RESPONSE) {
 			/* we get this from Windows 98 instead of 
 			   an error on SMB negprot response */
-			cFYI(1,("Negative RFC 1002 Session Response Error 0x%x)",temp[4]));
+			cFYI(1,("Negative RFC1002 Session Response Error 0x%x)",
+				temp[4]));
 			if(server->tcpStatus == CifsNew) {
 				/* if nack on negprot (rather than 
 				ret of smb negprot error) reconnecting
@@ -345,118 +465,140 @@
 			cifs_reconnect(server);
 			csocket = server->ssocket;
 			continue;
-		} else { /* we have an SMB response */
-			if((pdu_length > CIFSMaxBufSize + 
-				MAX_CIFS_HDR_SIZE - 4) ||
+		}
+
+		/* else we have an SMB response */
+		if((pdu_length > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE - 4) ||
 			    (pdu_length < sizeof (struct smb_hdr) - 1 - 4)) {
-				cERROR(1,
-				    ("Invalid size SMB length %d and pdu_length %d",
+			cERROR(1, ("Invalid size SMB length %d pdu_length %d",
 					length, pdu_length+4));
+			cifs_reconnect(server);
+			csocket = server->ssocket;
+			wake_up(&server->response_q);
+			continue;
+		} 
+
+		/* else length ok */
+		reconnect = 0;
+
+		if(pdu_length > MAX_CIFS_HDR_SIZE - 4) {
+			isLargeBuf = TRUE;
+			memcpy(bigbuf, smallbuf, 4);
+			smb_buffer = bigbuf;
+		}
+		length = 0;
+		iov.iov_base = 4 + (char *)smb_buffer;
+		iov.iov_len = pdu_length;
+		for (total_read = 0; total_read < pdu_length; 
+		     total_read += length) {
+			length = kernel_recvmsg(csocket, &smb_msg, &iov, 1,
+						pdu_length - total_read, 0);
+			if((server->tcpStatus == CifsExiting) ||
+			    (length == -EINTR)) {
+				/* then will exit */
+				reconnect = 2;
+				break;
+			} else if (server->tcpStatus == CifsNeedReconnect) {
 				cifs_reconnect(server);
 				csocket = server->ssocket;
-				wake_up(&server->response_q);
+			        /* Reconnect wakes up rspns q */
+				/* Now we will reread sock */
+				reconnect = 1;
+				break;
+			} else if ((length == -ERESTARTSYS) || 
+				   (length == -EAGAIN)) {
+				msleep(1); /* minimum sleep to prevent looping,
+                                              allowing socket to clear and app 
+					      threads to set tcpStatus
+					      CifsNeedReconnect if server hung*/
 				continue;
-			} else { /* length ok */
-				int reconnect = 0;
-
-				if(pdu_length > MAX_CIFS_HDR_SIZE - 4) {
-					isLargeBuf = TRUE;
-					memcpy(bigbuf, smallbuf, 4);
-					smb_buffer = bigbuf;
-				}
-				length = 0;
-				iov.iov_base = 4 + (char *)smb_buffer;
-				iov.iov_len = pdu_length;
-				for (total_read = 0;
-				     total_read < pdu_length;
-				     total_read += length) {
-					length = kernel_recvmsg(csocket, &smb_msg,
-						&iov, 1,
-						pdu_length - total_read, 0);
-					if((server->tcpStatus == CifsExiting) ||
-					    (length == -EINTR)) {
-						/* then will exit */
-						reconnect = 2;
-						break;
-					} else if (server->tcpStatus ==
-						    CifsNeedReconnect) {
-						cifs_reconnect(server);
-						csocket = server->ssocket;
-					        /* Reconnect wakes up rspns q */
-						/* Now we will reread sock */
-						reconnect = 1;
-						break;
-					} else if ((length == -ERESTARTSYS) || 
-						   (length == -EAGAIN)) {
-						msleep(1); /* minimum sleep to prevent looping
-                                                              allowing socket to clear and app threads to set
-                                                              tcpStatus CifsNeedReconnect if server hung */
-						continue;
-					} else if (length <= 0) {
-						cERROR(1,("Received no data, expecting %d",
-						      pdu_length - total_read));
-						cifs_reconnect(server);
-						csocket = server->ssocket;
-						reconnect = 1;
-						break;
-					}
-				}
-				if(reconnect == 2)
-				    break;
-				else if(reconnect == 1)
-				    continue;
-
-				length += 4; /* account for rfc1002 hdr */
-			}
-
-			dump_smb(smb_buffer, length);
-			if (checkSMB
-			    (smb_buffer, smb_buffer->Mid, total_read+4)) {
-				cERROR(1, ("Bad SMB Received "));
-				continue;
-			}
-
-
-			task_to_wake = NULL;
-			spin_lock(&GlobalMid_Lock);
-			list_for_each(tmp, &server->pending_mid_q) {
-				mid_entry = list_entry(tmp, struct mid_q_entry,
-							qhead);
-
-				if ((mid_entry->mid == smb_buffer->Mid)
-					&& (mid_entry->midState == 
-						MID_REQUEST_SUBMITTED) 
-					&& (mid_entry->command == 
-						smb_buffer->Command)) {
-					cFYI(1,("Found Mid 0x%x wake up"
-						,mid_entry->mid));
-					/* BB FIXME - missing code here BB */
-					/* check_2nd_t2(smb_buffer); */
-					task_to_wake = mid_entry->tsk;
-					mid_entry->resp_buf =
-					    smb_buffer;
-					mid_entry->midState =
-					    MID_RESPONSE_RECEIVED;
-					if(isLargeBuf)
-						mid_entry->largeBuf = 1;
-					else
-						mid_entry->largeBuf = 0;
-				}
-			}
-			spin_unlock(&GlobalMid_Lock);
-			if (task_to_wake) {
-				if(isLargeBuf)
-					bigbuf = NULL;
-				else
-					smallbuf = NULL;
-				smb_buffer = NULL; /* will be freed by users thread after he is done */
-				wake_up_process(task_to_wake);
-			} else if (is_valid_oplock_break(smb_buffer) == FALSE) {                          
-				cERROR(1, ("No task to wake, unknown frame rcvd!"));
-				cifs_dump_mem("Received Data is: ",temp,sizeof(struct smb_hdr));
+			} else if (length <= 0) {
+				cERROR(1,("Received no data, expecting %d",
+					      pdu_length - total_read));
+				cifs_reconnect(server);
+				csocket = server->ssocket;
+				reconnect = 1;
+				break;
 			}
 		}
-	}
+		if(reconnect == 2)
+			break;
+		else if(reconnect == 1)
+			continue;
+
+		length += 4; /* account for rfc1002 hdr */
+	
+
+		dump_smb(smb_buffer, length);
+		if (checkSMB (smb_buffer, smb_buffer->Mid, total_read+4)) {
+			cERROR(1, ("Bad SMB Received "));
+			continue;
+		}
+
+
+		task_to_wake = NULL;
+		spin_lock(&GlobalMid_Lock);
+		list_for_each(tmp, &server->pending_mid_q) {
+			mid_entry = list_entry(tmp, struct mid_q_entry, qhead);
+
+			if ((mid_entry->mid == smb_buffer->Mid) && 
+			    (mid_entry->midState == MID_REQUEST_SUBMITTED) &&
+			    (mid_entry->command == smb_buffer->Command)) {
+				cFYI(1,("Found Mid 0x%x wake", mid_entry->mid));
+					
+				if(check2ndT2(smb_buffer,server->maxBuf) > 0) {
+					/* We have a multipart transact2 resp */
+					if(mid_entry->resp_buf) {
+						/* merge response - fix up 1st*/
+						if(coalesce_t2(smb_buffer, 
+							mid_entry->resp_buf)) {
+							isMultiRsp = TRUE;
+							break;
+						} else {
+							/* all parts received */
+							goto multi_t2_fnd; 
+						}
+					} else {
+						if(!isLargeBuf) {
+							cERROR(1,("1st trans2 resp needs bigbuf"));
+					/* BB maybe we can fix this up,  switch
+				   	   to already allocated large buffer? */
+						} else {
+							mid_entry->resp_buf =
+								 smb_buffer;
+							mid_entry->largeBuf = 1;
+							isMultiRsp = TRUE;
+							bigbuf = NULL;
+						}
+					}
+					break;
+				} 
+				mid_entry->resp_buf = smb_buffer;
+				if(isLargeBuf)
+					mid_entry->largeBuf = 1;
+				else
+					mid_entry->largeBuf = 0;
+multi_t2_fnd:
+				task_to_wake = mid_entry->tsk;
+				mid_entry->midState = MID_RESPONSE_RECEIVED;
+				break;
+			}
+		}
+		spin_unlock(&GlobalMid_Lock);
+		if (task_to_wake) {
+			if(isLargeBuf)
+				bigbuf = NULL;
+			else
+				smallbuf = NULL;
+			/* smb buffer freed by user thread when done */
+			wake_up_process(task_to_wake);
+		} else if ((is_valid_oplock_break(smb_buffer) == FALSE) 
+		    && (isMultiRsp == FALSE)) {                          
+			cERROR(1, ("No task to wake, unknown frame rcvd!"));
+			cifs_dump_mem("Received Data is: ",temp,sizeof(struct smb_hdr));
+		}
+	} /* end while !EXITING */
+
 	spin_lock(&GlobalMid_Lock);
 	server->tcpStatus = CifsExiting;
 	server->tsk = NULL;