summaryrefslogtreecommitdiffstats
path: root/fs/cifs/smb2misc.c
diff options
context:
space:
mode:
authorPavel Shilovsky <pshilovsky@samba.org>2012-09-19 01:20:33 +0200
committerSteve French <smfrench@gmail.com>2012-09-25 04:46:30 +0200
commit983c88a497914d60c91f431b05a8449ddda19167 (patch)
tree6e8ce5d70c6a584472ed7414497d06c034eb0595 /fs/cifs/smb2misc.c
parentCIFS: Move oplock break to ops struct (diff)
downloadlinux-983c88a497914d60c91f431b05a8449ddda19167.tar.xz
linux-983c88a497914d60c91f431b05a8449ddda19167.zip
CIFS: Add oplock break support for SMB2
Signed-off-by: Pavel Shilovsky <pshilovsky@samba.org> Signed-off-by: Steve French <smfrench@gmail.com>
Diffstat (limited to 'fs/cifs/smb2misc.c')
-rw-r--r--fs/cifs/smb2misc.c76
1 files changed, 74 insertions, 2 deletions
diff --git a/fs/cifs/smb2misc.c b/fs/cifs/smb2misc.c
index 78225f517a60..01479a3fee8d 100644
--- a/fs/cifs/smb2misc.c
+++ b/fs/cifs/smb2misc.c
@@ -142,8 +142,8 @@ smb2_check_message(char *buf, unsigned int length)
}
if (smb2_rsp_struct_sizes[command] != pdu->StructureSize2) {
- if (hdr->Status == 0 ||
- pdu->StructureSize2 != SMB2_ERROR_STRUCTURE_SIZE2) {
+ if (command != SMB2_OPLOCK_BREAK_HE && (hdr->Status == 0 ||
+ pdu->StructureSize2 != SMB2_ERROR_STRUCTURE_SIZE2)) {
/* error packets have 9 byte structure size */
cERROR(1, "Illegal response size %u for command %d",
le16_to_cpu(pdu->StructureSize2), command);
@@ -162,6 +162,9 @@ smb2_check_message(char *buf, unsigned int length)
if (4 + len != clc_len) {
cFYI(1, "Calculated size %u length %u mismatch mid %llu",
clc_len, 4 + len, mid);
+ /* Windows 7 server returns 24 bytes more */
+ if (clc_len + 20 == len && command == SMB2_OPLOCK_BREAK_HE)
+ return 0;
/* server can return one byte more */
if (clc_len == 4 + len + 1)
return 0;
@@ -356,3 +359,72 @@ cifs_convert_path_to_utf16(const char *from, struct cifs_sb_info *cifs_sb)
CIFS_MOUNT_MAP_SPECIAL_CHR);
return to;
}
+
+bool
+smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server)
+{
+ struct smb2_oplock_break *rsp = (struct smb2_oplock_break *)buffer;
+ struct list_head *tmp, *tmp1, *tmp2;
+ struct cifs_ses *ses;
+ struct cifs_tcon *tcon;
+ struct cifsInodeInfo *cinode;
+ struct cifsFileInfo *cfile;
+
+ cFYI(1, "Checking for oplock break");
+
+ if (rsp->hdr.Command != SMB2_OPLOCK_BREAK)
+ return false;
+
+ if (le16_to_cpu(rsp->StructureSize) !=
+ smb2_rsp_struct_sizes[SMB2_OPLOCK_BREAK_HE]) {
+ return false;
+ }
+
+ cFYI(1, "oplock level 0x%d", rsp->OplockLevel);
+
+ /* look up tcon based on tid & uid */
+ spin_lock(&cifs_tcp_ses_lock);
+ list_for_each(tmp, &server->smb_ses_list) {
+ ses = list_entry(tmp, struct cifs_ses, smb_ses_list);
+ list_for_each(tmp1, &ses->tcon_list) {
+ tcon = list_entry(tmp1, struct cifs_tcon, tcon_list);
+
+ cifs_stats_inc(&tcon->stats.cifs_stats.num_oplock_brks);
+ spin_lock(&cifs_file_list_lock);
+ list_for_each(tmp2, &tcon->openFileList) {
+ cfile = list_entry(tmp2, struct cifsFileInfo,
+ tlist);
+ if (rsp->PersistentFid !=
+ cfile->fid.persistent_fid ||
+ rsp->VolatileFid !=
+ cfile->fid.volatile_fid)
+ continue;
+
+ cFYI(1, "file id match, oplock break");
+ cinode = CIFS_I(cfile->dentry->d_inode);
+
+ if (!cinode->clientCanCacheAll &&
+ rsp->OplockLevel == SMB2_OPLOCK_LEVEL_NONE)
+ cfile->oplock_break_cancelled = true;
+ else
+ cfile->oplock_break_cancelled = false;
+
+ smb2_set_oplock_level(cinode,
+ rsp->OplockLevel ? SMB2_OPLOCK_LEVEL_II : 0);
+
+ queue_work(cifsiod_wq, &cfile->oplock_break);
+
+ spin_unlock(&cifs_file_list_lock);
+ spin_unlock(&cifs_tcp_ses_lock);
+ return true;
+ }
+ spin_unlock(&cifs_file_list_lock);
+ spin_unlock(&cifs_tcp_ses_lock);
+ cFYI(1, "No matching file for oplock break");
+ return true;
+ }
+ }
+ spin_unlock(&cifs_tcp_ses_lock);
+ cFYI(1, "Can not process oplock break for non-existent connection");
+ return false;
+}