cifs: use FsFullSizeInfo in qfs since it is quota aware

Message ID 20180418014514.26919-1-lsahlber@redhat.com
State New
Headers show
Series
  • cifs: use FsFullSizeInfo in qfs since it is quota aware
Related show

Commit Message

Ronnie Sahlberg April 18, 2018, 1:45 a.m.
RHBZ:1505772

In SMB2 we already query FsFullSizeInfo for queryfs since this infolevel
is quota-aware.
SMB1 used FsSizeInfo but this infolevel does not provide info about quota
and thus it will cause 'df' and similar commands to show the wrong
amount of data for the share if quotas have been set.

This patch changes cifs_queryfs() to first try FsFullSizeInfo and only
fallback to FsSizeInfo if FsFullSizeInfo is not available.

This makes SMB1 'df' show the correct amounts for quota enabled shares.

Reported-by: Xiaoli Feng <xifeng@redhat.com>
Signed-off-by: Ronnie Sahlberg <lsahlber@redhat.com>
---
 fs/cifs/cifspdu.h   |  8 ++++++
 fs/cifs/cifsproto.h |  2 ++
 fs/cifs/cifssmb.c   | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 fs/cifs/smb1ops.c   |  6 ++++
 4 files changed, 96 insertions(+)

Patch

diff --git a/fs/cifs/cifspdu.h b/fs/cifs/cifspdu.h
index 1ce733f3582f..097b55f22250 100644
--- a/fs/cifs/cifspdu.h
+++ b/fs/cifs/cifspdu.h
@@ -2153,6 +2153,14 @@  struct aliasInfo92 {
 
 typedef struct {
 	__le64 TotalAllocationUnits;
+	__le64 CallerAvailableUnits;
+	__le64 ActualAvailableUnits;
+	__le32 SectorsPerAllocationUnit;
+	__le32 BytesPerSector;
+} __attribute__((packed)) FILE_SYSTEM_FULL_INFO; /* size info, level 0x3ef */
+
+typedef struct {
+	__le64 TotalAllocationUnits;
 	__le64 FreeAllocationUnits;
 	__le32 SectorsPerAllocationUnit;
 	__le32 BytesPerSector;
diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
index 365a414a75e9..f9b4dbbd1039 100644
--- a/fs/cifs/cifsproto.h
+++ b/fs/cifs/cifsproto.h
@@ -298,6 +298,8 @@  extern int parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size,
 extern void reset_cifs_unix_caps(unsigned int xid, struct cifs_tcon *tcon,
 				 struct cifs_sb_info *cifs_sb,
 				 struct smb_vol *vol);
+extern int CIFSSMBQFSFullInfo(const unsigned int xid, struct cifs_tcon *tcon,
+			      struct kstatfs *FSData);
 extern int CIFSSMBQFSInfo(const unsigned int xid, struct cifs_tcon *tcon,
 			struct kstatfs *FSData);
 extern int SMBOldQFSInfo(const unsigned int xid, struct cifs_tcon *tcon,
diff --git a/fs/cifs/cifssmb.c b/fs/cifs/cifssmb.c
index 6d3e40d7029c..b9e921eee4f0 100644
--- a/fs/cifs/cifssmb.c
+++ b/fs/cifs/cifssmb.c
@@ -5015,6 +5015,86 @@  SMBOldQFSInfo(const unsigned int xid, struct cifs_tcon *tcon,
 }
 
 int
+CIFSSMBQFSFullInfo(const unsigned int xid, struct cifs_tcon *tcon,
+		   struct kstatfs *FSData)
+{
+/* level 0x3ef SMB_QUERY_FILE_SYSTEM_FULL_INFO */
+	TRANSACTION2_QFSI_REQ *pSMB = NULL;
+	TRANSACTION2_QFSI_RSP *pSMBr = NULL;
+	FILE_SYSTEM_FULL_INFO *response_data;
+	int rc = 0;
+	int bytes_returned = 0;
+	__u16 params, byte_count;
+
+	cifs_dbg(FYI, "In QFSFullInfo\n");
+QFSInfoRetry:
+	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_QUERY_FS_FULL_SIZE_INFO);
+	inc_rfc1001_len(pSMB, 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) {
+		cifs_dbg(FYI, "Send error in QFSFullInfo = %d\n", rc);
+	} else {		/* decode response */
+		rc = validate_t2((struct smb_t2_rsp *)pSMBr);
+
+		if (rc || get_bcc(&pSMBr->hdr) < 24)
+			rc = -EIO;	/* bad smb */
+		else {
+			__u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset);
+
+			response_data =
+			    (FILE_SYSTEM_FULL_INFO
+			     *) (((char *) &pSMBr->hdr.Protocol) +
+				 data_offset);
+			FSData->f_bsize =
+			    le32_to_cpu(response_data->BytesPerSector) *
+			    le32_to_cpu(response_data->
+					SectorsPerAllocationUnit);
+			FSData->f_blocks =
+			    le64_to_cpu(response_data->TotalAllocationUnits);
+			FSData->f_bfree = FSData->f_bavail =
+			    le64_to_cpu(response_data->CallerAvailableUnits);
+			cifs_dbg(FYI, "Blocks: %lld  Free: %lld Block size %ld\n",
+				 (unsigned long long)FSData->f_blocks,
+				 (unsigned long long)FSData->f_bfree,
+				 FSData->f_bsize);
+		}
+	}
+	cifs_buf_release(pSMB);
+
+	if (rc == -EAGAIN)
+		goto QFSInfoRetry;
+
+	return rc;
+}
+
+int
 CIFSSMBQFSInfo(const unsigned int xid, struct cifs_tcon *tcon,
 	       struct kstatfs *FSData)
 {
diff --git a/fs/cifs/smb1ops.c b/fs/cifs/smb1ops.c
index aff8ce8ba34d..da723e2c1d85 100644
--- a/fs/cifs/smb1ops.c
+++ b/fs/cifs/smb1ops.c
@@ -900,6 +900,12 @@  cifs_queryfs(const unsigned int xid, struct cifs_tcon *tcon,
 		rc = CIFSSMBQFSPosixInfo(xid, tcon, buf);
 
 	/*
+	 * Try FsFullSizeInformation (as it is quota aware)
+	 */
+	if (rc && (tcon->ses->capabilities & CAP_NT_SMBS))
+		rc = CIFSSMBQFSFullInfo(xid, tcon, buf);
+
+	/*
 	 * Only need to call the old QFSInfo if failed on newer one,
 	 * e.g. by OS/2.
 	 **/