[CIFS] Add support for legacy servers part nine.  statfs (df and du) is now
functional, and the length check is fixed so readdir does not throw a
warning message when windows me messes up the response to FindFirst
of an empty dir (with only . and ..).

Signed-off-by: Steve French (sfrench@us.ibm.com)
diff --git a/fs/cifs/CHANGES b/fs/cifs/CHANGES
index 47ae68b..661b459 100644
--- a/fs/cifs/CHANGES
+++ b/fs/cifs/CHANGES
@@ -3,7 +3,8 @@
 Fix readdir caching when unlink removes file in current search buffer,
 and this is followed by a rewind search to just before the deleted entry.
 Do not attempt to set ctime unless atime and/or mtime change requested
-(most servers throw it away anyway).
+(most servers throw it away anyway). Fix length check of received smbs
+to be more accurate.
 
 Version 1.36
 ------------
diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c
index f738c8b..1f97d39 100644
--- a/fs/cifs/cifsfs.c
+++ b/fs/cifs/cifsfs.c
@@ -205,6 +205,10 @@
 #endif /* CIFS_EXPERIMENTAL */
 	rc = CIFSSMBQFSInfo(xid, pTcon, buf);
 
+	/* Old Windows servers do not support level 103, retry with level 
+	   one if old server failed the previous call */ 
+	if(rc)
+		rc = SMBOldQFSInfo(xid, pTcon, buf);
 	/*     
 	   int f_type;
 	   __fsid_t f_fsid;
diff --git a/fs/cifs/cifspdu.h b/fs/cifs/cifspdu.h
index cf46659..3fa3779 100644
--- a/fs/cifs/cifspdu.h
+++ b/fs/cifs/cifspdu.h
@@ -1684,6 +1684,14 @@
 } FILE_SYSTEM_INFO;		/* size info, level 0x103 */
 
 typedef struct {
+	__le32 fsid;
+	__le32 SectorsPerAllocationUnit;
+	__le32 TotalAllocationUnits;
+	__le32 FreeAllocationUnits;
+	__le16  BytesPerSector;
+} FILE_SYSTEM_ALLOC_INFO;
+
+typedef struct {
 	__le16 MajorVersionNumber;
 	__le16 MinorVersionNumber;
 	__le64 Capability;
diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
index 6943f7c..0bace38 100644
--- a/fs/cifs/cifsproto.h
+++ b/fs/cifs/cifsproto.h
@@ -133,6 +133,8 @@
 			int remap);
 extern int CIFSSMBQFSInfo(const int xid, struct cifsTconInfo *tcon,
 			struct kstatfs *FSData);
+extern int SMBOldQFSInfo(const int xid, struct cifsTconInfo *tcon,
+			struct kstatfs *FSData);
 extern int CIFSSMBSetFSUnixInfo(const int xid, struct cifsTconInfo *tcon,
 			__u64 cap);
 
diff --git a/fs/cifs/cifssmb.c b/fs/cifs/cifssmb.c
index f72a61d..daf717e 100644
--- a/fs/cifs/cifssmb.c
+++ b/fs/cifs/cifssmb.c
@@ -3215,6 +3215,92 @@
 	return rc;
 }
 
+/* Query File System Info such as free space to old servers such as Win 9x */
+int
+SMBOldQFSInfo(const int xid, struct cifsTconInfo *tcon, struct kstatfs *FSData)
+{
+/* level 0x01 SMB_QUERY_FILE_SYSTEM_INFO */
+	TRANSACTION2_QFSI_REQ *pSMB = NULL;
+	TRANSACTION2_QFSI_RSP *pSMBr = NULL;
+	FILE_SYSTEM_ALLOC_INFO *response_data;
+	int rc = 0;
+	int bytes_returned = 0;
+	__u16 params, byte_count;
+
+	cFYI(1, ("OldQFSInfo"));
+oldQFSInfoRetry:
+	rc = smb_init(SMB_COM_TRANSACTION2, 15, tcon, (void **) &pSMB,
+		(void **) &pSMBr);
+	if (rc)
+		return rc;
+	rc = smb_init(SMB_COM_TRANSACTION2, 15, tcon, (void **) &pSMB,
+		      (void **) &pSMBr);
+	if (rc)
+		return rc;
+
+	params = 2;     /* level */
+	pSMB->TotalDataCount = 0;
+	pSMB->MaxParameterCount = cpu_to_le16(2);
+	pSMB->MaxDataCount = cpu_to_le16(1000);
+	pSMB->MaxSetupCount = 0;
+	pSMB->Reserved = 0;
+	pSMB->Flags = 0;
+	pSMB->Timeout = 0;
+	pSMB->Reserved2 = 0;
+	byte_count = params + 1 /* pad */ ;
+	pSMB->TotalParameterCount = cpu_to_le16(params);
+	pSMB->ParameterCount = pSMB->TotalParameterCount;
+	pSMB->ParameterOffset = cpu_to_le16(offsetof(
+	struct smb_com_transaction2_qfsi_req, InformationLevel) - 4);
+	pSMB->DataCount = 0;
+	pSMB->DataOffset = 0;
+	pSMB->SetupCount = 1;
+	pSMB->Reserved3 = 0;
+	pSMB->SubCommand = cpu_to_le16(TRANS2_QUERY_FS_INFORMATION);
+	pSMB->InformationLevel = cpu_to_le16(SMB_INFO_ALLOCATION);
+	pSMB->hdr.smb_buf_length += byte_count;
+	pSMB->ByteCount = cpu_to_le16(byte_count);
+
+	rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB,
+		(struct smb_hdr *) pSMBr, &bytes_returned, 0);
+	if (rc) {
+		cFYI(1, ("Send error in QFSInfo = %d", rc));
+	} else {                /* decode response */
+		rc = validate_t2((struct smb_t2_rsp *)pSMBr);
+
+		if (rc || (pSMBr->ByteCount < 18))
+			rc = -EIO;      /* bad smb */
+		else {
+			__u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset);
+			cFYI(1,("qfsinf resp BCC: %d  Offset %d",
+				 pSMBr->ByteCount, data_offset));
+
+			response_data =
+				(FILE_SYSTEM_ALLOC_INFO *) 
+				(((char *) &pSMBr->hdr.Protocol) + data_offset);
+			FSData->f_bsize =
+				le16_to_cpu(response_data->BytesPerSector) *
+				le32_to_cpu(response_data->
+					SectorsPerAllocationUnit);
+			FSData->f_blocks =
+				le32_to_cpu(response_data->TotalAllocationUnits);
+			FSData->f_bfree = FSData->f_bavail =
+				le32_to_cpu(response_data->FreeAllocationUnits);
+			cFYI(1,
+			     ("Blocks: %lld  Free: %lld Block size %ld",
+			      (unsigned long long)FSData->f_blocks,
+			      (unsigned long long)FSData->f_bfree,
+			      FSData->f_bsize));
+		}
+	}
+	cifs_buf_release(pSMB);
+
+	if (rc == -EAGAIN)
+		goto oldQFSInfoRetry;
+
+	return rc;
+}
+
 int
 CIFSSMBQFSInfo(const int xid, struct cifsTconInfo *tcon, struct kstatfs *FSData)
 {
@@ -3236,7 +3322,7 @@
 	params = 2;	/* level */
 	pSMB->TotalDataCount = 0;
 	pSMB->MaxParameterCount = cpu_to_le16(2);
-	pSMB->MaxDataCount = cpu_to_le16(1000);	/* BB find exact max SMB PDU from sess structure BB */
+	pSMB->MaxDataCount = cpu_to_le16(1000);
 	pSMB->MaxSetupCount = 0;
 	pSMB->Reserved = 0;
 	pSMB->Flags = 0;
@@ -3259,17 +3345,14 @@
 	rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB,
 			 (struct smb_hdr *) pSMBr, &bytes_returned, 0);
 	if (rc) {
-		cERROR(1, ("Send error in QFSInfo = %d", rc));
+		cFYI(1, ("Send error in QFSInfo = %d", rc));
 	} else {		/* decode response */
                 rc = validate_t2((struct smb_t2_rsp *)pSMBr);
 
-		if (rc || (pSMBr->ByteCount < 24)) /* BB alsO CHEck enough total bytes returned */
+		if (rc || (pSMBr->ByteCount < 24))
 			rc = -EIO;	/* bad smb */
 		else {
 			__u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset);
-			cFYI(1,
-				("Decoding qfsinfo response.  BCC: %d  Offset %d",
-				pSMBr->ByteCount, data_offset));
 
 			response_data =
 			    (FILE_SYSTEM_INFO
diff --git a/fs/cifs/misc.c b/fs/cifs/misc.c
index fafbdbf..26b35b5 100644
--- a/fs/cifs/misc.c
+++ b/fs/cifs/misc.c
@@ -450,13 +450,12 @@
 
 	if ((4 + len != smbCalcSize(smb))
 	    || (4 + len != (unsigned int)length)) {
-		return 0;
-	} else {
 		cERROR(1, ("smbCalcSize %x ", smbCalcSize(smb)));
 		cERROR(1,
 		       ("bad smb size detected. The Mid=%d", smb->Mid));
 		return 1;
 	}
+	return 0;
 }
 int
 is_valid_oplock_break(struct smb_hdr *buf)
diff --git a/fs/cifs/netmisc.c b/fs/cifs/netmisc.c
index 873b812..32efa32 100644
--- a/fs/cifs/netmisc.c
+++ b/fs/cifs/netmisc.c
@@ -868,7 +868,7 @@
 smbCalcSize(struct smb_hdr *ptr)
 {
 	return (sizeof (struct smb_hdr) + (2 * ptr->WordCount) +
-		BCC(ptr));
+		2 /* size of the bcc field itself */ + BCC(ptr));
 }
 
 /* The following are taken from fs/ntfs/util.c */