Commit 9bd45408 authored by Pavel Shilovsky's avatar Pavel Shilovsky Committed by Steve French

CIFS: Properly process SMB3 lease breaks

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: default avatarPavel Shilovsky <pshilov@microsoft.com>
Signed-off-by: default avatarSteve French <stfrench@microsoft.com>
parent 32546a95
...@@ -269,8 +269,9 @@ struct smb_version_operations { ...@@ -269,8 +269,9 @@ struct smb_version_operations {
int (*check_message)(char *, unsigned int, struct TCP_Server_Info *); int (*check_message)(char *, unsigned int, struct TCP_Server_Info *);
bool (*is_oplock_break)(char *, struct TCP_Server_Info *); bool (*is_oplock_break)(char *, struct TCP_Server_Info *);
int (*handle_cancelled_mid)(char *, struct TCP_Server_Info *); int (*handle_cancelled_mid)(char *, struct TCP_Server_Info *);
void (*downgrade_oplock)(struct TCP_Server_Info *, void (*downgrade_oplock)(struct TCP_Server_Info *server,
struct cifsInodeInfo *, bool); struct cifsInodeInfo *cinode, __u32 oplock,
unsigned int epoch, bool *purge_cache);
/* process transaction2 response */ /* process transaction2 response */
bool (*check_trans2)(struct mid_q_entry *, struct TCP_Server_Info *, bool (*check_trans2)(struct mid_q_entry *, struct TCP_Server_Info *,
char *, int); char *, int);
...@@ -1303,6 +1304,8 @@ struct cifsFileInfo { ...@@ -1303,6 +1304,8 @@ struct cifsFileInfo {
unsigned int f_flags; unsigned int f_flags;
bool invalidHandle:1; /* file closed via session abend */ bool invalidHandle:1; /* file closed via session abend */
bool oplock_break_cancelled:1; 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; int count;
spinlock_t file_info_lock; /* protects four flag/count fields above */ spinlock_t file_info_lock; /* protects four flag/count fields above */
struct mutex fh_mutex; /* prevents reopen race after dead ses*/ struct mutex fh_mutex; /* prevents reopen race after dead ses*/
...@@ -1450,7 +1453,7 @@ struct cifsInodeInfo { ...@@ -1450,7 +1453,7 @@ struct cifsInodeInfo {
unsigned int epoch; /* used to track lease state changes */ unsigned int epoch; /* used to track lease state changes */
#define CIFS_INODE_PENDING_OPLOCK_BREAK (0) /* oplock break in progress */ #define CIFS_INODE_PENDING_OPLOCK_BREAK (0) /* oplock break in progress */
#define CIFS_INODE_PENDING_WRITERS (1) /* Writes 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_DELETE_PENDING (3) /* delete pending on server */
#define CIFS_INO_INVALID_MAPPING (4) /* pagecache is invalid */ #define CIFS_INO_INVALID_MAPPING (4) /* pagecache is invalid */
#define CIFS_INO_LOCK (5) /* lock bit for synchronization */ #define CIFS_INO_LOCK (5) /* lock bit for synchronization */
......
...@@ -4730,12 +4730,13 @@ void cifs_oplock_break(struct work_struct *work) ...@@ -4730,12 +4730,13 @@ void cifs_oplock_break(struct work_struct *work)
struct cifs_tcon *tcon = tlink_tcon(cfile->tlink); struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
struct TCP_Server_Info *server = tcon->ses->server; struct TCP_Server_Info *server = tcon->ses->server;
int rc = 0; int rc = 0;
bool purge_cache = false;
wait_on_bit(&cinode->flags, CIFS_INODE_PENDING_WRITERS, wait_on_bit(&cinode->flags, CIFS_INODE_PENDING_WRITERS,
TASK_UNINTERRUPTIBLE); TASK_UNINTERRUPTIBLE);
server->ops->downgrade_oplock(server, cinode, server->ops->downgrade_oplock(server, cinode, cfile->oplock_level,
test_bit(CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2, &cinode->flags)); cfile->oplock_epoch, &purge_cache);
if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) && if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
cifs_has_mand_locks(cinode)) { cifs_has_mand_locks(cinode)) {
...@@ -4750,18 +4751,21 @@ void cifs_oplock_break(struct work_struct *work) ...@@ -4750,18 +4751,21 @@ void cifs_oplock_break(struct work_struct *work)
else else
break_lease(inode, O_WRONLY); break_lease(inode, O_WRONLY);
rc = filemap_fdatawrite(inode->i_mapping); rc = filemap_fdatawrite(inode->i_mapping);
if (!CIFS_CACHE_READ(cinode)) { if (!CIFS_CACHE_READ(cinode) || purge_cache) {
rc = filemap_fdatawait(inode->i_mapping); rc = filemap_fdatawait(inode->i_mapping);
mapping_set_error(inode->i_mapping, rc); mapping_set_error(inode->i_mapping, rc);
cifs_zap_mapping(inode); cifs_zap_mapping(inode);
} }
cifs_dbg(FYI, "Oplock flush inode %p rc %d\n", inode, rc); 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); rc = cifs_push_locks(cfile);
if (rc) if (rc)
cifs_dbg(VFS, "Push locks rc = %d\n", rc); cifs_dbg(VFS, "Push locks rc = %d\n", rc);
oplock_break_ack:
/* /*
* releasing stale oplock after recent reconnect of smb session using * releasing stale oplock after recent reconnect of smb session using
* a now incorrect file handle is not a data integrity issue but do * a now incorrect file handle is not a data integrity issue but do
......
...@@ -488,21 +488,10 @@ is_valid_oplock_break(char *buffer, struct TCP_Server_Info *srv) ...@@ -488,21 +488,10 @@ is_valid_oplock_break(char *buffer, struct TCP_Server_Info *srv)
set_bit(CIFS_INODE_PENDING_OPLOCK_BREAK, set_bit(CIFS_INODE_PENDING_OPLOCK_BREAK,
&pCifsInode->flags); &pCifsInode->flags);
/* netfile->oplock_epoch = 0;
* Set flag if the server downgrades the oplock netfile->oplock_level = pSMB->OplockLevel;
* 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_break_cancelled = false; netfile->oplock_break_cancelled = false;
cifs_queue_oplock_break(netfile);
spin_unlock(&tcon->open_file_lock); spin_unlock(&tcon->open_file_lock);
spin_unlock(&cifs_tcp_ses_lock); spin_unlock(&cifs_tcp_ses_lock);
......
...@@ -369,12 +369,10 @@ coalesce_t2(char *second_buf, struct smb_hdr *target_hdr) ...@@ -369,12 +369,10 @@ coalesce_t2(char *second_buf, struct smb_hdr *target_hdr)
static void static void
cifs_downgrade_oplock(struct TCP_Server_Info *server, 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);
cifs_set_oplock_level(cinode, OPLOCK_READ);
else
cifs_set_oplock_level(cinode, 0);
} }
static bool static bool
......
...@@ -529,7 +529,7 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp, ...@@ -529,7 +529,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, "found in the open list\n");
cifs_dbg(FYI, "lease key match, lease break 0x%x\n", cifs_dbg(FYI, "lease key match, lease break 0x%x\n",
le32_to_cpu(rsp->NewLeaseState)); lease_state);
if (ack_req) if (ack_req)
cfile->oplock_break_cancelled = false; cfile->oplock_break_cancelled = false;
...@@ -538,17 +538,8 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp, ...@@ -538,17 +538,8 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp,
set_bit(CIFS_INODE_PENDING_OPLOCK_BREAK, &cinode->flags); set_bit(CIFS_INODE_PENDING_OPLOCK_BREAK, &cinode->flags);
/* cfile->oplock_epoch = le16_to_cpu(rsp->Epoch);
* Set or clear flags depending on the lease state being READ. cfile->oplock_level = lease_state;
* 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);
cifs_queue_oplock_break(cfile); cifs_queue_oplock_break(cfile);
kfree(lw); kfree(lw);
...@@ -571,7 +562,7 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp, ...@@ -571,7 +562,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, "found in the pending open list\n");
cifs_dbg(FYI, "lease key match, lease break 0x%x\n", cifs_dbg(FYI, "lease key match, lease break 0x%x\n",
le32_to_cpu(rsp->NewLeaseState)); lease_state);
open->oplock = lease_state; open->oplock = lease_state;
} }
...@@ -696,18 +687,9 @@ smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server) ...@@ -696,18 +687,9 @@ smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server)
set_bit(CIFS_INODE_PENDING_OPLOCK_BREAK, set_bit(CIFS_INODE_PENDING_OPLOCK_BREAK,
&cinode->flags); &cinode->flags);
/* cfile->oplock_epoch = 0;
* Set flag if the server downgrades the oplock cfile->oplock_level = rsp->OplockLevel;
* 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);
spin_unlock(&cfile->file_info_lock); spin_unlock(&cfile->file_info_lock);
cifs_queue_oplock_break(cfile); cifs_queue_oplock_break(cfile);
......
...@@ -3282,22 +3282,38 @@ static long smb3_fallocate(struct file *file, struct cifs_tcon *tcon, int mode, ...@@ -3282,22 +3282,38 @@ static long smb3_fallocate(struct file *file, struct cifs_tcon *tcon, int mode,
static void static void
smb2_downgrade_oplock(struct TCP_Server_Info *server, 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, oplock, 0, NULL);
server->ops->set_oplock_level(cinode, SMB2_OPLOCK_LEVEL_II,
0, NULL);
else
server->ops->set_oplock_level(cinode, 0, 0, NULL);
} }
static void static void
smb21_downgrade_oplock(struct TCP_Server_Info *server, smb21_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock,
struct cifsInodeInfo *cinode, bool set_level2) 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, unsigned int old_state = cinode->oplock;
set_level2 ? SMB2_LEASE_READ_CACHING_HE : unsigned int old_epoch = cinode->epoch;
0, 0, NULL); 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 static void
...@@ -4559,7 +4575,7 @@ struct smb_version_operations smb21_operations = { ...@@ -4559,7 +4575,7 @@ struct smb_version_operations smb21_operations = {
.print_stats = smb2_print_stats, .print_stats = smb2_print_stats,
.is_oplock_break = smb2_is_valid_oplock_break, .is_oplock_break = smb2_is_valid_oplock_break,
.handle_cancelled_mid = smb2_handle_cancelled_mid, .handle_cancelled_mid = smb2_handle_cancelled_mid,
.downgrade_oplock = smb21_downgrade_oplock, .downgrade_oplock = smb2_downgrade_oplock,
.need_neg = smb2_need_neg, .need_neg = smb2_need_neg,
.negotiate = smb2_negotiate, .negotiate = smb2_negotiate,
.negotiate_wsize = smb2_negotiate_wsize, .negotiate_wsize = smb2_negotiate_wsize,
...@@ -4659,7 +4675,7 @@ struct smb_version_operations smb30_operations = { ...@@ -4659,7 +4675,7 @@ struct smb_version_operations smb30_operations = {
.dump_share_caps = smb2_dump_share_caps, .dump_share_caps = smb2_dump_share_caps,
.is_oplock_break = smb2_is_valid_oplock_break, .is_oplock_break = smb2_is_valid_oplock_break,
.handle_cancelled_mid = smb2_handle_cancelled_mid, .handle_cancelled_mid = smb2_handle_cancelled_mid,
.downgrade_oplock = smb21_downgrade_oplock, .downgrade_oplock = smb3_downgrade_oplock,
.need_neg = smb2_need_neg, .need_neg = smb2_need_neg,
.negotiate = smb2_negotiate, .negotiate = smb2_negotiate,
.negotiate_wsize = smb3_negotiate_wsize, .negotiate_wsize = smb3_negotiate_wsize,
...@@ -4767,7 +4783,7 @@ struct smb_version_operations smb311_operations = { ...@@ -4767,7 +4783,7 @@ struct smb_version_operations smb311_operations = {
.dump_share_caps = smb2_dump_share_caps, .dump_share_caps = smb2_dump_share_caps,
.is_oplock_break = smb2_is_valid_oplock_break, .is_oplock_break = smb2_is_valid_oplock_break,
.handle_cancelled_mid = smb2_handle_cancelled_mid, .handle_cancelled_mid = smb2_handle_cancelled_mid,
.downgrade_oplock = smb21_downgrade_oplock, .downgrade_oplock = smb3_downgrade_oplock,
.need_neg = smb2_need_neg, .need_neg = smb2_need_neg,
.negotiate = smb2_negotiate, .negotiate = smb2_negotiate,
.negotiate_wsize = smb3_negotiate_wsize, .negotiate_wsize = smb3_negotiate_wsize,
......
...@@ -1386,7 +1386,7 @@ struct smb2_oplock_break { ...@@ -1386,7 +1386,7 @@ struct smb2_oplock_break {
struct smb2_lease_break { struct smb2_lease_break {
struct smb2_sync_hdr sync_hdr; struct smb2_sync_hdr sync_hdr;
__le16 StructureSize; /* Must be 44 */ __le16 StructureSize; /* Must be 44 */
__le16 Reserved; __le16 Epoch;
__le32 Flags; __le32 Flags;
__u8 LeaseKey[16]; __u8 LeaseKey[16];
__le32 CurrentLeaseState; __le32 CurrentLeaseState;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment