CIFS: Add SMB2.1 lease break support
authorPavel Shilovsky <pshilovsky@samba.org>
Wed, 19 Sep 2012 13:22:45 +0000 (06:22 -0700)
committerSteve French <smfrench@gmail.com>
Tue, 25 Sep 2012 02:46:33 +0000 (21:46 -0500)
Signed-off-by: Pavel Shilovsky <pshilovsky@samba.org>
Signed-off-by: Steve French <sfrench@us.ibm.com>
fs/cifs/smb2file.c
fs/cifs/smb2misc.c
fs/cifs/smb2ops.c
fs/cifs/smb2pdu.c
fs/cifs/smb2pdu.h
fs/cifs/smb2proto.h

index 78fb2050e0d6e705455fdda7f239b73d3fadcc96..a93eec30a50d2dab9626025fda82b4f9b62c9c88 100644 (file)
@@ -38,6 +38,8 @@ void
 smb2_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock)
 {
        oplock &= 0xFF;
+       if (oplock == SMB2_OPLOCK_LEVEL_NOCHANGE)
+               return;
        if (oplock == SMB2_OPLOCK_LEVEL_EXCLUSIVE) {
                cinode->clientCanCacheAll = true;
                cinode->clientCanCacheRead = true;
index 01479a3fee8d5c9d346f24719652fcb7a8a77a55..3a7f8bd5127d3db113ceca3b4f4e38d35e340083 100644 (file)
@@ -148,6 +148,13 @@ smb2_check_message(char *buf, unsigned int length)
                        cERROR(1, "Illegal response size %u for command %d",
                                   le16_to_cpu(pdu->StructureSize2), command);
                        return 1;
+               } else if (command == SMB2_OPLOCK_BREAK_HE && (hdr->Status == 0)
+                          && (le16_to_cpu(pdu->StructureSize2) != 44)
+                          && (le16_to_cpu(pdu->StructureSize2) != 36)) {
+                       /* special case for SMB2.1 lease break message */
+                       cERROR(1, "Illegal response size %d for oplock break",
+                                  le16_to_cpu(pdu->StructureSize2));
+                       return 1;
                }
        }
 
@@ -360,6 +367,84 @@ cifs_convert_path_to_utf16(const char *from, struct cifs_sb_info *cifs_sb)
        return to;
 }
 
+__le32
+smb2_get_lease_state(struct cifsInodeInfo *cinode)
+{
+       if (cinode->clientCanCacheAll)
+               return SMB2_LEASE_WRITE_CACHING | SMB2_LEASE_READ_CACHING;
+       else if (cinode->clientCanCacheRead)
+               return SMB2_LEASE_READ_CACHING;
+       return 0;
+}
+
+__u8 smb2_map_lease_to_oplock(__le32 lease_state)
+{
+       if (lease_state & SMB2_LEASE_WRITE_CACHING) {
+               if (lease_state & SMB2_LEASE_HANDLE_CACHING)
+                       return SMB2_OPLOCK_LEVEL_BATCH;
+               else
+                       return SMB2_OPLOCK_LEVEL_EXCLUSIVE;
+       } else if (lease_state & SMB2_LEASE_READ_CACHING)
+               return SMB2_OPLOCK_LEVEL_II;
+       return 0;
+}
+
+static bool
+smb2_is_valid_lease_break(char *buffer, struct TCP_Server_Info *server)
+{
+       struct smb2_lease_break *rsp = (struct smb2_lease_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 lease break");
+
+       /* 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);
+                               cinode = CIFS_I(cfile->dentry->d_inode);
+
+                               if (memcmp(cinode->lease_key, rsp->LeaseKey,
+                                          SMB2_LEASE_KEY_SIZE))
+                                       continue;
+
+                               cFYI(1, "lease key match, lease break 0x%d",
+                                    le32_to_cpu(rsp->NewLeaseState));
+
+                               smb2_set_oplock_level(cinode,
+                                 smb2_map_lease_to_oplock(rsp->NewLeaseState));
+
+                               if (rsp->Flags &
+                                   SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED)
+                                       cfile->oplock_break_cancelled = false;
+                               else
+                                       cfile->oplock_break_cancelled = true;
+
+                               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, "Can not process lease break - no lease matched");
+       return false;
+}
+
 bool
 smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server)
 {
@@ -377,7 +462,10 @@ smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server)
 
        if (le16_to_cpu(rsp->StructureSize) !=
                                smb2_rsp_struct_sizes[SMB2_OPLOCK_BREAK_HE]) {
-               return false;
+               if (le16_to_cpu(rsp->StructureSize) == 44)
+                       return smb2_is_valid_lease_break(buffer, server);
+               else
+                       return false;
        }
 
        cFYI(1, "oplock level 0x%d", rsp->OplockLevel);
index 360d9079af499277a9ad3d920c5ba58b51768946..630156f98cc70489b55065e1ca140601cd9a31d5 100644 (file)
@@ -513,6 +513,10 @@ static int
 smb2_oplock_response(struct cifs_tcon *tcon, struct cifs_fid *fid,
                     struct cifsInodeInfo *cinode)
 {
+       if (tcon->ses->server->capabilities & SMB2_GLOBAL_CAP_LEASING)
+               return SMB2_lease_break(0, tcon, cinode->lease_key,
+                                       smb2_get_lease_state(cinode));
+
        return SMB2_oplock_break(0, tcon, fid->persistent_fid,
                                 fid->volatile_fid,
                                 cinode->clientCanCacheRead ? 1 : 0);
index 89d2824587b2be6ea43935d2dd83f699ce0b25e6..1572abefb3782646e5dcb50bc568159f9f7a4b86 100644 (file)
@@ -911,7 +911,6 @@ parse_lease_state(struct smb2_create_rsp *rsp)
 {
        char *data_offset;
        struct create_lease *lc;
-       __u8 oplock = 0;
        bool found = false;
 
        data_offset = (char *)rsp;
@@ -932,19 +931,9 @@ parse_lease_state(struct smb2_create_rsp *rsp)
        } while (le32_to_cpu(lc->ccontext.Next) != 0);
 
        if (!found)
-               return oplock;
-
-       if (le32_to_cpu(lc->lcontext.LeaseState) & SMB2_LEASE_WRITE_CACHING) {
-               if (le32_to_cpu(lc->lcontext.LeaseState) &
-                                               SMB2_LEASE_HANDLE_CACHING)
-                       oplock = SMB2_OPLOCK_LEVEL_BATCH;
-               else
-                       oplock = SMB2_OPLOCK_LEVEL_EXCLUSIVE;
-       } else if (le32_to_cpu(lc->lcontext.LeaseState) &
-                                               SMB2_LEASE_READ_CACHING)
-               oplock = SMB2_OPLOCK_LEVEL_II;
+               return 0;
 
-       return oplock;
+       return smb2_map_lease_to_oplock(lc->lcontext.LeaseState);
 }
 
 int
@@ -2228,3 +2217,34 @@ SMB2_lock(const unsigned int xid, struct cifs_tcon *tcon,
 
        return smb2_lockv(xid, tcon, persist_fid, volatile_fid, pid, 1, &lock);
 }
+
+int
+SMB2_lease_break(const unsigned int xid, struct cifs_tcon *tcon,
+                __u8 *lease_key, const __le32 lease_state)
+{
+       int rc;
+       struct smb2_lease_ack *req = NULL;
+
+       cFYI(1, "SMB2_lease_break");
+       rc = small_smb2_init(SMB2_OPLOCK_BREAK, tcon, (void **) &req);
+
+       if (rc)
+               return rc;
+
+       req->hdr.CreditRequest = cpu_to_le16(1);
+       req->StructureSize = cpu_to_le16(36);
+       inc_rfc1001_len(req, 12);
+
+       memcpy(req->LeaseKey, lease_key, 16);
+       req->LeaseState = lease_state;
+
+       rc = SendReceiveNoRsp(xid, tcon->ses, (char *) req, CIFS_OBREAK_OP);
+       /* SMB2 buffer freed by function above */
+
+       if (rc) {
+               cifs_stats_fail_inc(tcon, SMB2_OPLOCK_BREAK_HE);
+               cFYI(1, "Send error in Lease Break = %d", rc);
+       }
+
+       return rc;
+}
index e818a5cc5bd8c8e0c9204f1e7cc330f14f5dbb8b..da099225b1a94b2d6f2477819fa2e4e2c4efd7f1 100644 (file)
@@ -693,6 +693,31 @@ struct smb2_oplock_break {
        __u64  VolatileFid;
 } __packed;
 
+#define SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED cpu_to_le32(0x01)
+
+struct smb2_lease_break {
+       struct smb2_hdr hdr;
+       __le16 StructureSize; /* Must be 44 */
+       __le16 Reserved;
+       __le32 Flags;
+       __u8   LeaseKey[16];
+       __le32 CurrentLeaseState;
+       __le32 NewLeaseState;
+       __le32 BreakReason;
+       __le32 AccessMaskHint;
+       __le32 ShareMaskHint;
+} __packed;
+
+struct smb2_lease_ack {
+       struct smb2_hdr hdr;
+       __le16 StructureSize; /* Must be 36 */
+       __le16 Reserved;
+       __le32 Flags;
+       __u8   LeaseKey[16];
+       __le32 LeaseState;
+       __le64 LeaseDuration;
+} __packed;
+
 /*
  *     PDU infolevel structure definitions
  *     BB consider moving to a different header
index 8b4d3712255bc98316f7f23d1e75514a14408e64..7d25f8b14f937c51b6f95777b07556a36b190610 100644 (file)
@@ -48,6 +48,8 @@ extern struct mid_q_entry *smb2_setup_request(struct cifs_ses *ses,
 extern struct mid_q_entry *smb2_setup_async_request(
                        struct TCP_Server_Info *server, struct smb_rqst *rqst);
 extern void smb2_echo_request(struct work_struct *work);
+extern __le32 smb2_get_lease_state(struct cifsInodeInfo *cinode);
+extern __u8 smb2_map_lease_to_oplock(__le32 lease_state);
 extern bool smb2_is_valid_oplock_break(char *buffer,
                                       struct TCP_Server_Info *srv);
 
@@ -151,5 +153,7 @@ extern int smb2_lockv(const unsigned int xid, struct cifs_tcon *tcon,
                      const __u64 persist_fid, const __u64 volatile_fid,
                      const __u32 pid, const __u32 num_lock,
                      struct smb2_lock_element *buf);
+extern int SMB2_lease_break(const unsigned int xid, struct cifs_tcon *tcon,
+                           __u8 *lease_key, const __le32 lease_state);
 
 #endif                 /* _SMB2PROTO_H */