Skip to content

Commit 9bd4540

Browse files
piastrysmfrench
authored andcommitted
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: Pavel Shilovsky <[email protected]> Signed-off-by: Steve French <[email protected]>
1 parent 32546a9 commit 9bd4540

File tree

7 files changed

+57
-65
lines changed

7 files changed

+57
-65
lines changed

fs/cifs/cifsglob.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,9 @@ struct smb_version_operations {
269269
int (*check_message)(char *, unsigned int, struct TCP_Server_Info *);
270270
bool (*is_oplock_break)(char *, struct TCP_Server_Info *);
271271
int (*handle_cancelled_mid)(char *, struct TCP_Server_Info *);
272-
void (*downgrade_oplock)(struct TCP_Server_Info *,
273-
struct cifsInodeInfo *, bool);
272+
void (*downgrade_oplock)(struct TCP_Server_Info *server,
273+
struct cifsInodeInfo *cinode, __u32 oplock,
274+
unsigned int epoch, bool *purge_cache);
274275
/* process transaction2 response */
275276
bool (*check_trans2)(struct mid_q_entry *, struct TCP_Server_Info *,
276277
char *, int);
@@ -1303,6 +1304,8 @@ struct cifsFileInfo {
13031304
unsigned int f_flags;
13041305
bool invalidHandle:1; /* file closed via session abend */
13051306
bool oplock_break_cancelled:1;
1307+
unsigned int oplock_epoch; /* epoch from the lease break */
1308+
__u32 oplock_level; /* oplock/lease level from the lease break */
13061309
int count;
13071310
spinlock_t file_info_lock; /* protects four flag/count fields above */
13081311
struct mutex fh_mutex; /* prevents reopen race after dead ses*/
@@ -1450,7 +1453,7 @@ struct cifsInodeInfo {
14501453
unsigned int epoch; /* used to track lease state changes */
14511454
#define CIFS_INODE_PENDING_OPLOCK_BREAK (0) /* oplock break in progress */
14521455
#define CIFS_INODE_PENDING_WRITERS (1) /* Writes in progress */
1453-
#define CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2 (2) /* Downgrade oplock to L2 */
1456+
#define CIFS_INODE_FLAG_UNUSED (2) /* Unused flag */
14541457
#define CIFS_INO_DELETE_PENDING (3) /* delete pending on server */
14551458
#define CIFS_INO_INVALID_MAPPING (4) /* pagecache is invalid */
14561459
#define CIFS_INO_LOCK (5) /* lock bit for synchronization */

fs/cifs/file.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4730,12 +4730,13 @@ void cifs_oplock_break(struct work_struct *work)
47304730
struct cifs_tcon *tcon = tlink_tcon(cfile->tlink);
47314731
struct TCP_Server_Info *server = tcon->ses->server;
47324732
int rc = 0;
4733+
bool purge_cache = false;
47334734

47344735
wait_on_bit(&cinode->flags, CIFS_INODE_PENDING_WRITERS,
47354736
TASK_UNINTERRUPTIBLE);
47364737

4737-
server->ops->downgrade_oplock(server, cinode,
4738-
test_bit(CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2, &cinode->flags));
4738+
server->ops->downgrade_oplock(server, cinode, cfile->oplock_level,
4739+
cfile->oplock_epoch, &purge_cache);
47394740

47404741
if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
47414742
cifs_has_mand_locks(cinode)) {
@@ -4750,18 +4751,21 @@ void cifs_oplock_break(struct work_struct *work)
47504751
else
47514752
break_lease(inode, O_WRONLY);
47524753
rc = filemap_fdatawrite(inode->i_mapping);
4753-
if (!CIFS_CACHE_READ(cinode)) {
4754+
if (!CIFS_CACHE_READ(cinode) || purge_cache) {
47544755
rc = filemap_fdatawait(inode->i_mapping);
47554756
mapping_set_error(inode->i_mapping, rc);
47564757
cifs_zap_mapping(inode);
47574758
}
47584759
cifs_dbg(FYI, "Oplock flush inode %p rc %d\n", inode, rc);
4760+
if (CIFS_CACHE_WRITE(cinode))
4761+
goto oplock_break_ack;
47594762
}
47604763

47614764
rc = cifs_push_locks(cfile);
47624765
if (rc)
47634766
cifs_dbg(VFS, "Push locks rc = %d\n", rc);
47644767

4768+
oplock_break_ack:
47654769
/*
47664770
* releasing stale oplock after recent reconnect of smb session using
47674771
* a now incorrect file handle is not a data integrity issue but do

fs/cifs/misc.c

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -488,21 +488,10 @@ is_valid_oplock_break(char *buffer, struct TCP_Server_Info *srv)
488488
set_bit(CIFS_INODE_PENDING_OPLOCK_BREAK,
489489
&pCifsInode->flags);
490490

491-
/*
492-
* Set flag if the server downgrades the oplock
493-
* to L2 else clear.
494-
*/
495-
if (pSMB->OplockLevel)
496-
set_bit(
497-
CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
498-
&pCifsInode->flags);
499-
else
500-
clear_bit(
501-
CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
502-
&pCifsInode->flags);
503-
504-
cifs_queue_oplock_break(netfile);
491+
netfile->oplock_epoch = 0;
492+
netfile->oplock_level = pSMB->OplockLevel;
505493
netfile->oplock_break_cancelled = false;
494+
cifs_queue_oplock_break(netfile);
506495

507496
spin_unlock(&tcon->open_file_lock);
508497
spin_unlock(&cifs_tcp_ses_lock);

fs/cifs/smb1ops.c

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -369,12 +369,10 @@ coalesce_t2(char *second_buf, struct smb_hdr *target_hdr)
369369

370370
static void
371371
cifs_downgrade_oplock(struct TCP_Server_Info *server,
372-
struct cifsInodeInfo *cinode, bool set_level2)
372+
struct cifsInodeInfo *cinode, __u32 oplock,
373+
unsigned int epoch, bool *purge_cache)
373374
{
374-
if (set_level2)
375-
cifs_set_oplock_level(cinode, OPLOCK_READ);
376-
else
377-
cifs_set_oplock_level(cinode, 0);
375+
cifs_set_oplock_level(cinode, oplock);
378376
}
379377

380378
static bool

fs/cifs/smb2misc.c

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp,
529529

530530
cifs_dbg(FYI, "found in the open list\n");
531531
cifs_dbg(FYI, "lease key match, lease break 0x%x\n",
532-
le32_to_cpu(rsp->NewLeaseState));
532+
lease_state);
533533

534534
if (ack_req)
535535
cfile->oplock_break_cancelled = false;
@@ -538,17 +538,8 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp,
538538

539539
set_bit(CIFS_INODE_PENDING_OPLOCK_BREAK, &cinode->flags);
540540

541-
/*
542-
* Set or clear flags depending on the lease state being READ.
543-
* HANDLE caching flag should be added when the client starts
544-
* to defer closing remote file handles with HANDLE leases.
545-
*/
546-
if (lease_state & SMB2_LEASE_READ_CACHING_HE)
547-
set_bit(CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
548-
&cinode->flags);
549-
else
550-
clear_bit(CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
551-
&cinode->flags);
541+
cfile->oplock_epoch = le16_to_cpu(rsp->Epoch);
542+
cfile->oplock_level = lease_state;
552543

553544
cifs_queue_oplock_break(cfile);
554545
kfree(lw);
@@ -571,7 +562,7 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp,
571562

572563
cifs_dbg(FYI, "found in the pending open list\n");
573564
cifs_dbg(FYI, "lease key match, lease break 0x%x\n",
574-
le32_to_cpu(rsp->NewLeaseState));
565+
lease_state);
575566

576567
open->oplock = lease_state;
577568
}
@@ -696,18 +687,9 @@ smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server)
696687
set_bit(CIFS_INODE_PENDING_OPLOCK_BREAK,
697688
&cinode->flags);
698689

699-
/*
700-
* Set flag if the server downgrades the oplock
701-
* to L2 else clear.
702-
*/
703-
if (rsp->OplockLevel)
704-
set_bit(
705-
CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
706-
&cinode->flags);
707-
else
708-
clear_bit(
709-
CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
710-
&cinode->flags);
690+
cfile->oplock_epoch = 0;
691+
cfile->oplock_level = rsp->OplockLevel;
692+
711693
spin_unlock(&cfile->file_info_lock);
712694

713695
cifs_queue_oplock_break(cfile);

fs/cifs/smb2ops.c

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3282,22 +3282,38 @@ static long smb3_fallocate(struct file *file, struct cifs_tcon *tcon, int mode,
32823282

32833283
static void
32843284
smb2_downgrade_oplock(struct TCP_Server_Info *server,
3285-
struct cifsInodeInfo *cinode, bool set_level2)
3285+
struct cifsInodeInfo *cinode, __u32 oplock,
3286+
unsigned int epoch, bool *purge_cache)
32863287
{
3287-
if (set_level2)
3288-
server->ops->set_oplock_level(cinode, SMB2_OPLOCK_LEVEL_II,
3289-
0, NULL);
3290-
else
3291-
server->ops->set_oplock_level(cinode, 0, 0, NULL);
3288+
server->ops->set_oplock_level(cinode, oplock, 0, NULL);
32923289
}
32933290

32943291
static void
3295-
smb21_downgrade_oplock(struct TCP_Server_Info *server,
3296-
struct cifsInodeInfo *cinode, bool set_level2)
3292+
smb21_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock,
3293+
unsigned int epoch, bool *purge_cache);
3294+
3295+
static void
3296+
smb3_downgrade_oplock(struct TCP_Server_Info *server,
3297+
struct cifsInodeInfo *cinode, __u32 oplock,
3298+
unsigned int epoch, bool *purge_cache)
32973299
{
3298-
server->ops->set_oplock_level(cinode,
3299-
set_level2 ? SMB2_LEASE_READ_CACHING_HE :
3300-
0, 0, NULL);
3300+
unsigned int old_state = cinode->oplock;
3301+
unsigned int old_epoch = cinode->epoch;
3302+
unsigned int new_state;
3303+
3304+
if (epoch > old_epoch) {
3305+
smb21_set_oplock_level(cinode, oplock, 0, NULL);
3306+
cinode->epoch = epoch;
3307+
}
3308+
3309+
new_state = cinode->oplock;
3310+
*purge_cache = false;
3311+
3312+
if ((old_state & CIFS_CACHE_READ_FLG) != 0 &&
3313+
(new_state & CIFS_CACHE_READ_FLG) == 0)
3314+
*purge_cache = true;
3315+
else if (old_state == new_state && (epoch - old_epoch > 1))
3316+
*purge_cache = true;
33013317
}
33023318

33033319
static void
@@ -4559,7 +4575,7 @@ struct smb_version_operations smb21_operations = {
45594575
.print_stats = smb2_print_stats,
45604576
.is_oplock_break = smb2_is_valid_oplock_break,
45614577
.handle_cancelled_mid = smb2_handle_cancelled_mid,
4562-
.downgrade_oplock = smb21_downgrade_oplock,
4578+
.downgrade_oplock = smb2_downgrade_oplock,
45634579
.need_neg = smb2_need_neg,
45644580
.negotiate = smb2_negotiate,
45654581
.negotiate_wsize = smb2_negotiate_wsize,
@@ -4659,7 +4675,7 @@ struct smb_version_operations smb30_operations = {
46594675
.dump_share_caps = smb2_dump_share_caps,
46604676
.is_oplock_break = smb2_is_valid_oplock_break,
46614677
.handle_cancelled_mid = smb2_handle_cancelled_mid,
4662-
.downgrade_oplock = smb21_downgrade_oplock,
4678+
.downgrade_oplock = smb3_downgrade_oplock,
46634679
.need_neg = smb2_need_neg,
46644680
.negotiate = smb2_negotiate,
46654681
.negotiate_wsize = smb3_negotiate_wsize,
@@ -4767,7 +4783,7 @@ struct smb_version_operations smb311_operations = {
47674783
.dump_share_caps = smb2_dump_share_caps,
47684784
.is_oplock_break = smb2_is_valid_oplock_break,
47694785
.handle_cancelled_mid = smb2_handle_cancelled_mid,
4770-
.downgrade_oplock = smb21_downgrade_oplock,
4786+
.downgrade_oplock = smb3_downgrade_oplock,
47714787
.need_neg = smb2_need_neg,
47724788
.negotiate = smb2_negotiate,
47734789
.negotiate_wsize = smb3_negotiate_wsize,

fs/cifs/smb2pdu.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1386,7 +1386,7 @@ struct smb2_oplock_break {
13861386
struct smb2_lease_break {
13871387
struct smb2_sync_hdr sync_hdr;
13881388
__le16 StructureSize; /* Must be 44 */
1389-
__le16 Reserved;
1389+
__le16 Epoch;
13901390
__le32 Flags;
13911391
__u8 LeaseKey[16];
13921392
__le32 CurrentLeaseState;

0 commit comments

Comments
 (0)