If a mount to a server is using multichannel, an oplock break arriving on a secondary channel won't find the open file (since it won't find the tcon for it), and this will cause each oplock break on secondary channels to time out, slowing performance drastically. Fix smb2_is_valid_oplock_break so that if it is a secondary channel and an oplock break was not found, check for tcons (and the files hanging off the tcons) on the primary channel. Fixes xfstest generic/013 to ksmbd Cc: <stable@xxxxxxxxxxxxxxx> -- Thanks, Steve
From 0186c0d4c418251f066cc8c8d80f16783f9ded00 Mon Sep 17 00:00:00 2001 From: Steve French <stfrench@xxxxxxxxxxxxx> Date: Fri, 28 Oct 2022 01:13:16 -0500 Subject: [PATCH] smb3: fix oplock break when using multichannel If a mount to a server is using multichannel, an oplock break arriving on a secondary channel won't find the open file (since it won't find the tcon for it). Fix smb2_is_valid_oplock_break so that if it is a secondary channel and an oplock break was not found, check for tcons (and the files hanging off the tcons) on the primary chanel. Fixes xfstest generic/013 to ksmbd Cc: <stable@xxxxxxxxxxxxxxx> Signed-off-by: Steve French <stfrench@xxxxxxxxxxxxx> --- fs/cifs/smb2misc.c | 47 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/fs/cifs/smb2misc.c b/fs/cifs/smb2misc.c index a38720477966..ccd294e6c9d9 100644 --- a/fs/cifs/smb2misc.c +++ b/fs/cifs/smb2misc.c @@ -689,13 +689,56 @@ smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server) return false; } - cifs_dbg(FYI, "oplock level 0x%x\n", rsp->OplockLevel); + cifs_dbg(FYI, "oplock level 0x%x fid 0x%llx\n", + rsp->OplockLevel, rsp->PersistentFid); /* look up tcon based on tid & uid */ spin_lock(&cifs_tcp_ses_lock); list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) { list_for_each_entry(tcon, &ses->tcon_list, tcon_list) { + spin_lock(&tcon->open_file_lock); + list_for_each_entry(cfile, &tcon->openFileList, tlist) { + if (rsp->PersistentFid != + cfile->fid.persistent_fid || + rsp->VolatileFid != + cfile->fid.volatile_fid) + continue; + + cifs_dbg(FYI, "file id match, oplock break\n"); + cifs_stats_inc( + &tcon->stats.cifs_stats.num_oplock_brks); + cinode = CIFS_I(d_inode(cfile->dentry)); + spin_lock(&cfile->file_info_lock); + if (!CIFS_CACHE_WRITE(cinode) && + rsp->OplockLevel == SMB2_OPLOCK_LEVEL_NONE) + cfile->oplock_break_cancelled = true; + else + cfile->oplock_break_cancelled = false; + set_bit(CIFS_INODE_PENDING_OPLOCK_BREAK, + &cinode->flags); + + cfile->oplock_epoch = 0; + cfile->oplock_level = rsp->OplockLevel; + + spin_unlock(&cfile->file_info_lock); + + cifs_queue_oplock_break(cfile); + + spin_unlock(&tcon->open_file_lock); + spin_unlock(&cifs_tcp_ses_lock); + return true; + } + spin_unlock(&tcon->open_file_lock); + } + } + + /* if file not found and secondary, check primary channel */ + if (server->primary_server == NULL) + goto valid_oplock_not_found; + + list_for_each_entry(ses, &server->primary_server->smb_ses_list, smb_ses_list) { + list_for_each_entry(tcon, &ses->tcon_list, tcon_list) { spin_lock(&tcon->open_file_lock); list_for_each_entry(cfile, &tcon->openFileList, tlist) { if (rsp->PersistentFid != @@ -732,6 +775,8 @@ smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server) spin_unlock(&tcon->open_file_lock); } } + +valid_oplock_not_found: spin_unlock(&cifs_tcp_ses_lock); cifs_dbg(FYI, "No file id matched, oplock break ignored\n"); trace_smb3_oplock_not_found(0 /* no xid */, rsp->PersistentFid, -- 2.34.1