CIFS: Properly process SMB3 lease breaks
authorPavel Shilovsky <pshilov@microsoft.com>
Tue, 29 Oct 2019 23:51:19 +0000 (16:51 -0700)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 1 Oct 2020 18:40:04 +0000 (20:40 +0200)
[ Upstream commit 9bd4540836684013aaad6070a65d6fcdd9006625 ]

Currenly we doesn't assume that a server may break a lease
from RWH to RW which causes us setting a wrong lease state
on a file and thus mistakenly flushing data and byte-range
locks and purging cached data on the client. This leads to
performance degradation because subsequent IOs go directly
to the server.

Fix this by propagating new lease state and epoch values
to the oplock break handler through cifsFileInfo structure
and removing the use of cifsInodeInfo flags for that. It
allows to avoid some races of several lease/oplock breaks
using those flags in parallel.

Signed-off-by: Pavel Shilovsky <pshilov@microsoft.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
fs/cifs/cifsglob.h
fs/cifs/file.c
fs/cifs/misc.c
fs/cifs/smb1ops.c
fs/cifs/smb2misc.c
fs/cifs/smb2ops.c
fs/cifs/smb2pdu.h

index 7ae21ad420fbfb40e89a7a12ca9184a869149b4e..a12258c32e8a390e3afe7fa2326317d6a2ccc48a 100644 (file)
@@ -242,8 +242,9 @@ struct smb_version_operations {
        int (*check_message)(char *, unsigned int, struct TCP_Server_Info *);
        bool (*is_oplock_break)(char *, struct TCP_Server_Info *);
        int (*handle_cancelled_mid)(char *, struct TCP_Server_Info *);
-       void (*downgrade_oplock)(struct TCP_Server_Info *,
-                                       struct cifsInodeInfo *, bool);
+       void (*downgrade_oplock)(struct TCP_Server_Info *server,
+                                struct cifsInodeInfo *cinode, __u32 oplock,
+                                unsigned int epoch, bool *purge_cache);
        /* process transaction2 response */
        bool (*check_trans2)(struct mid_q_entry *, struct TCP_Server_Info *,
                             char *, int);
@@ -1080,6 +1081,8 @@ struct cifsFileInfo {
        unsigned int f_flags;
        bool invalidHandle:1;   /* file closed via session abend */
        bool oplock_break_cancelled:1;
+       unsigned int oplock_epoch; /* epoch from the lease break */
+       __u32 oplock_level; /* oplock/lease level from the lease break */
        int count;
        spinlock_t file_info_lock; /* protects four flag/count fields above */
        struct mutex fh_mutex; /* prevents reopen race after dead ses*/
@@ -1191,7 +1194,7 @@ struct cifsInodeInfo {
        unsigned int epoch;             /* used to track lease state changes */
 #define CIFS_INODE_PENDING_OPLOCK_BREAK   (0) /* oplock break in progress */
 #define CIFS_INODE_PENDING_WRITERS       (1) /* Writes in progress */
-#define CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2 (2) /* Downgrade oplock to L2 */
+#define CIFS_INODE_FLAG_UNUSED           (2) /* Unused flag */
 #define CIFS_INO_DELETE_PENDING                  (3) /* delete pending on server */
 #define CIFS_INO_INVALID_MAPPING         (4) /* pagecache is invalid */
 #define CIFS_INO_LOCK                    (5) /* lock bit for synchronization */
index b2919166855f50ceced28ea3dee05b0c86b28220..dca78b6e9ea32b85866ff287c85eebdd813614d4 100644 (file)
@@ -3912,12 +3912,13 @@ void cifs_oplock_break(struct work_struct *work)
        struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
        struct TCP_Server_Info *server = tcon->ses->server;
        int rc = 0;
+       bool purge_cache = false;
 
        wait_on_bit(&cinode->flags, CIFS_INODE_PENDING_WRITERS,
                        TASK_UNINTERRUPTIBLE);
 
-       server->ops->downgrade_oplock(server, cinode,
-               test_bit(CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2, &cinode->flags));
+       server->ops->downgrade_oplock(server, cinode, cfile->oplock_level,
+                                     cfile->oplock_epoch, &purge_cache);
 
        if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
                                                cifs_has_mand_locks(cinode)) {
@@ -3932,18 +3933,21 @@ void cifs_oplock_break(struct work_struct *work)
                else
                        break_lease(inode, O_WRONLY);
                rc = filemap_fdatawrite(inode->i_mapping);
-               if (!CIFS_CACHE_READ(cinode)) {
+               if (!CIFS_CACHE_READ(cinode) || purge_cache) {
                        rc = filemap_fdatawait(inode->i_mapping);
                        mapping_set_error(inode->i_mapping, rc);
                        cifs_zap_mapping(inode);
                }
                cifs_dbg(FYI, "Oplock flush inode %p rc %d\n", inode, rc);
+               if (CIFS_CACHE_WRITE(cinode))
+                       goto oplock_break_ack;
        }
 
        rc = cifs_push_locks(cfile);
        if (rc)
                cifs_dbg(VFS, "Push locks rc = %d\n", rc);
 
+oplock_break_ack:
        /*
         * releasing stale oplock after recent reconnect of smb session using
         * a now incorrect file handle is not a data integrity issue but do
index 5e75df69062d8eee60ab029e9147136c7d3eccfe..bdf151e9491663e53304ed8ad31cfde17e420b95 100644 (file)
@@ -481,21 +481,10 @@ is_valid_oplock_break(char *buffer, struct TCP_Server_Info *srv)
                                set_bit(CIFS_INODE_PENDING_OPLOCK_BREAK,
                                        &pCifsInode->flags);
 
-                               /*
-                                * Set flag if the server downgrades the oplock
-                                * to L2 else clear.
-                                */
-                               if (pSMB->OplockLevel)
-                                       set_bit(
-                                          CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
-                                          &pCifsInode->flags);
-                               else
-                                       clear_bit(
-                                          CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
-                                          &pCifsInode->flags);
-
-                               cifs_queue_oplock_break(netfile);
+                               netfile->oplock_epoch = 0;
+                               netfile->oplock_level = pSMB->OplockLevel;
                                netfile->oplock_break_cancelled = false;
+                               cifs_queue_oplock_break(netfile);
 
                                spin_unlock(&tcon->open_file_lock);
                                spin_unlock(&cifs_tcp_ses_lock);
index 6f5d78b172bac2edcbe75f3c1e913d36197a5d8e..9a1f01c2f02091d738f8b07ca5ab53a044a391c3 100644 (file)
@@ -378,12 +378,10 @@ coalesce_t2(char *second_buf, struct smb_hdr *target_hdr)
 
 static void
 cifs_downgrade_oplock(struct TCP_Server_Info *server,
-                       struct cifsInodeInfo *cinode, bool set_level2)
+                     struct cifsInodeInfo *cinode, __u32 oplock,
+                     unsigned int epoch, bool *purge_cache)
 {
-       if (set_level2)
-               cifs_set_oplock_level(cinode, OPLOCK_READ);
-       else
-               cifs_set_oplock_level(cinode, 0);
+       cifs_set_oplock_level(cinode, oplock);
 }
 
 static bool
index 7b7b47e26dbd41113d6035ae7380a6598987a058..bddb2d7b3982447f9b847bc25a344a874e3a3c0b 100644 (file)
@@ -491,7 +491,7 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp,
 
                cifs_dbg(FYI, "found in the open list\n");
                cifs_dbg(FYI, "lease key match, lease break 0x%x\n",
-                        le32_to_cpu(rsp->NewLeaseState));
+                        lease_state);
 
                if (ack_req)
                        cfile->oplock_break_cancelled = false;
@@ -500,17 +500,8 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp,
 
                set_bit(CIFS_INODE_PENDING_OPLOCK_BREAK, &cinode->flags);
 
-               /*
-                * Set or clear flags depending on the lease state being READ.
-                * HANDLE caching flag should be added when the client starts
-                * to defer closing remote file handles with HANDLE leases.
-                */
-               if (lease_state & SMB2_LEASE_READ_CACHING_HE)
-                       set_bit(CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
-                               &cinode->flags);
-               else
-                       clear_bit(CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
-                                 &cinode->flags);
+               cfile->oplock_epoch = le16_to_cpu(rsp->Epoch);
+               cfile->oplock_level = lease_state;
 
                cifs_queue_oplock_break(cfile);
                kfree(lw);
@@ -533,7 +524,7 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp,
 
                cifs_dbg(FYI, "found in the pending open list\n");
                cifs_dbg(FYI, "lease key match, lease break 0x%x\n",
-                        le32_to_cpu(rsp->NewLeaseState));
+                        lease_state);
 
                open->oplock = lease_state;
        }
@@ -645,18 +636,9 @@ smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server)
                                set_bit(CIFS_INODE_PENDING_OPLOCK_BREAK,
                                        &cinode->flags);
 
-                               /*
-                                * Set flag if the server downgrades the oplock
-                                * to L2 else clear.
-                                */
-                               if (rsp->OplockLevel)
-                                       set_bit(
-                                          CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
-                                          &cinode->flags);
-                               else
-                                       clear_bit(
-                                          CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
-                                          &cinode->flags);
+                               cfile->oplock_epoch = 0;
+                               cfile->oplock_level = rsp->OplockLevel;
+
                                spin_unlock(&cfile->file_info_lock);
 
                                cifs_queue_oplock_break(cfile);
index edd4c7292be00e6bd2f0bbaa32c1bde7e232db02..67edd6e03f803ab079721e2e7f8f97516f1eccbd 100644 (file)
@@ -1379,22 +1379,38 @@ static long smb3_fallocate(struct file *file, struct cifs_tcon *tcon, int mode,
 
 static void
 smb2_downgrade_oplock(struct TCP_Server_Info *server,
-                       struct cifsInodeInfo *cinode, bool set_level2)
+                     struct cifsInodeInfo *cinode, __u32 oplock,
+                     unsigned int epoch, bool *purge_cache)
 {
-       if (set_level2)
-               server->ops->set_oplock_level(cinode, SMB2_OPLOCK_LEVEL_II,
-                                               0, NULL);
-       else
-               server->ops->set_oplock_level(cinode, 0, 0, NULL);
+       server->ops->set_oplock_level(cinode, oplock, 0, NULL);
 }
 
 static void
-smb21_downgrade_oplock(struct TCP_Server_Info *server,
-                      struct cifsInodeInfo *cinode, bool set_level2)
+smb21_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock,
+                      unsigned int epoch, bool *purge_cache);
+
+static void
+smb3_downgrade_oplock(struct TCP_Server_Info *server,
+                      struct cifsInodeInfo *cinode, __u32 oplock,
+                      unsigned int epoch, bool *purge_cache)
 {
-       server->ops->set_oplock_level(cinode,
-                                     set_level2 ? SMB2_LEASE_READ_CACHING_HE :
-                                     0, 0, NULL);
+       unsigned int old_state = cinode->oplock;
+       unsigned int old_epoch = cinode->epoch;
+       unsigned int new_state;
+
+       if (epoch > old_epoch) {
+               smb21_set_oplock_level(cinode, oplock, 0, NULL);
+               cinode->epoch = epoch;
+       }
+
+       new_state = cinode->oplock;
+       *purge_cache = false;
+
+       if ((old_state & CIFS_CACHE_READ_FLG) != 0 &&
+           (new_state & CIFS_CACHE_READ_FLG) == 0)
+               *purge_cache = true;
+       else if (old_state == new_state && (epoch - old_epoch > 1))
+               *purge_cache = true;
 }
 
 static void
@@ -1709,7 +1725,7 @@ struct smb_version_operations smb21_operations = {
        .print_stats = smb2_print_stats,
        .is_oplock_break = smb2_is_valid_oplock_break,
        .handle_cancelled_mid = smb2_handle_cancelled_mid,
-       .downgrade_oplock = smb21_downgrade_oplock,
+       .downgrade_oplock = smb2_downgrade_oplock,
        .need_neg = smb2_need_neg,
        .negotiate = smb2_negotiate,
        .negotiate_wsize = smb2_negotiate_wsize,
@@ -1793,7 +1809,7 @@ struct smb_version_operations smb30_operations = {
        .dump_share_caps = smb2_dump_share_caps,
        .is_oplock_break = smb2_is_valid_oplock_break,
        .handle_cancelled_mid = smb2_handle_cancelled_mid,
-       .downgrade_oplock = smb21_downgrade_oplock,
+       .downgrade_oplock = smb3_downgrade_oplock,
        .need_neg = smb2_need_neg,
        .negotiate = smb2_negotiate,
        .negotiate_wsize = smb2_negotiate_wsize,
@@ -1883,7 +1899,7 @@ struct smb_version_operations smb311_operations = {
        .dump_share_caps = smb2_dump_share_caps,
        .is_oplock_break = smb2_is_valid_oplock_break,
        .handle_cancelled_mid = smb2_handle_cancelled_mid,
-       .downgrade_oplock = smb21_downgrade_oplock,
+       .downgrade_oplock = smb3_downgrade_oplock,
        .need_neg = smb2_need_neg,
        .negotiate = smb2_negotiate,
        .negotiate_wsize = smb2_negotiate_wsize,
index 1af7afae3ad189d3f01c46a1dfe81436aadd5876..1a0c480745738172f5bb4d990c96fcf5611f2afa 100644 (file)
@@ -1025,7 +1025,7 @@ struct smb2_oplock_break {
 struct smb2_lease_break {
        struct smb2_hdr hdr;
        __le16 StructureSize; /* Must be 44 */
-       __le16 Reserved;
+       __le16 Epoch;
        __le32 Flags;
        __u8   LeaseKey[16];
        __le32 CurrentLeaseState;