пт, 29 мар. 2019 г. в 02:49, Aurelien Aptel <aaptel@xxxxxxxx>: > > In the oplock break handler, writing pending changes from pages puts > the FileInfo handle. If the refcount reaches zero it closes the handle > and waits for any oplock break handler to return, thus causing a deadlock. > > To prevent this situation: > > * We add a wait flag to cifsFileInfo_put() to decide whether we should > wait for running/pending oplock break handlers > > * We keep an additionnal reference of the SMB FileInfo handle so that > for the rest of the handler putting the handle won't close it. > - The ref is bumped everytime we queue the handler via the > cifs_queue_oplock_break() helper. > - The ref is decremented at the end of the handler > > This bug was triggered by xfstest 464. > > Signed-off-by: Aurelien Aptel <aaptel@xxxxxxxx> > --- > changes since v2: > > * add _cifsFileInfo_put() and make cifsFileInfo_put() a wrapper of it > > fs/cifs/cifsglob.h | 2 ++ > fs/cifs/file.c | 30 +++++++++++++++++++++++++----- > fs/cifs/misc.c | 25 +++++++++++++++++++++++-- > fs/cifs/smb2misc.c | 6 +++--- > 4 files changed, 53 insertions(+), 10 deletions(-) > > diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h > index 38feae812b47..ce0b1e8bc50c 100644 > --- a/fs/cifs/cifsglob.h > +++ b/fs/cifs/cifsglob.h > @@ -1325,6 +1325,7 @@ cifsFileInfo_get_locked(struct cifsFileInfo *cifs_file) > } > > struct cifsFileInfo *cifsFileInfo_get(struct cifsFileInfo *cifs_file); > +void _cifsFileInfo_put(struct cifsFileInfo *cifs_file, bool wait_oplock_hdlr); > void cifsFileInfo_put(struct cifsFileInfo *cifs_file); > > #define CIFS_CACHE_READ_FLG 1 > @@ -1847,6 +1848,7 @@ GLOBAL_EXTERN spinlock_t gidsidlock; > #endif /* CONFIG_CIFS_ACL */ > > void cifs_oplock_break(struct work_struct *work); > +void cifs_queue_oplock_break(struct cifsFileInfo *cfile); > > extern const struct slow_work_ops cifs_oplock_break_ops; > extern struct workqueue_struct *cifsiod_wq; > diff --git a/fs/cifs/file.c b/fs/cifs/file.c > index 89006e044973..9c0ccc06d172 100644 > --- a/fs/cifs/file.c > +++ b/fs/cifs/file.c > @@ -360,12 +360,30 @@ cifsFileInfo_get(struct cifsFileInfo *cifs_file) > return cifs_file; > } > > -/* > - * Release a reference on the file private data. This may involve closing > - * the filehandle out on the server. Must be called without holding > - * tcon->open_file_lock and cifs_file->file_info_lock. > +/** > + * cifsFileInfo_put - release a reference of file priv data > + * > + * Always potentially wait for oplock handler. See _cifsFileInfo_put(). > */ > void cifsFileInfo_put(struct cifsFileInfo *cifs_file) > +{ > + _cifsFileInfo_put(cifs_file, true); > +} > + > +/** > + * _cifsFileInfo_put - release a reference of file priv data > + * > + * This may involve closing the filehandle @cifs_file out on the > + * server. Must be called without holding tcon->open_file_lock and > + * cifs_file->file_info_lock. > + * > + * If @wait_for_oplock_handler is true and we are releasing the last > + * reference, wait for any running oplock break handler of the file > + * and cancel any pending one. If calling this function from the > + * oplock break handler, you need to pass false. > + * > + */ > +void _cifsFileInfo_put(struct cifsFileInfo *cifs_file, bool wait_oplock_handler) > { > struct inode *inode = d_inode(cifs_file->dentry); > struct cifs_tcon *tcon = tlink_tcon(cifs_file->tlink); > @@ -414,7 +432,8 @@ void cifsFileInfo_put(struct cifsFileInfo *cifs_file) > > spin_unlock(&tcon->open_file_lock); > > - oplock_break_cancelled = cancel_work_sync(&cifs_file->oplock_break); > + oplock_break_cancelled = wait_oplock_handler ? > + cancel_work_sync(&cifs_file->oplock_break) : false; > > if (!tcon->need_reconnect && !cifs_file->invalidHandle) { > struct TCP_Server_Info *server = tcon->ses->server; > @@ -4603,6 +4622,7 @@ void cifs_oplock_break(struct work_struct *work) > cinode); > cifs_dbg(FYI, "Oplock release rc = %d\n", rc); > } > + _cifsFileInfo_put(cfile, false /* do not wait for ourself */); > cifs_done_oplock_break(cinode); > } > > diff --git a/fs/cifs/misc.c b/fs/cifs/misc.c > index bee203055b30..1e1626a2cfc3 100644 > --- a/fs/cifs/misc.c > +++ b/fs/cifs/misc.c > @@ -501,8 +501,7 @@ is_valid_oplock_break(char *buffer, struct TCP_Server_Info *srv) > CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2, > &pCifsInode->flags); > > - queue_work(cifsoplockd_wq, > - &netfile->oplock_break); > + cifs_queue_oplock_break(netfile); > netfile->oplock_break_cancelled = false; > > spin_unlock(&tcon->open_file_lock); > @@ -607,6 +606,28 @@ void cifs_put_writer(struct cifsInodeInfo *cinode) > spin_unlock(&cinode->writers_lock); > } > > +/** > + * cifs_queue_oplock_break - queue the oplock break handler for cfile > + * > + * This function is called from the demultiplex thread when it > + * receives an oplock break for @cfile. > + * > + * Assumes the tcon->open_file_lock is held. > + * Assumes cfile->file_info_lock is NOT held. > + */ > +void cifs_queue_oplock_break(struct cifsFileInfo *cfile) > +{ > + /* > + * Bump the handle refcount now while we hold the > + * open_file_lock to enforce the validity of it for the oplock > + * break handler. The matching put is done at the end of the > + * handler. > + */ > + cifsFileInfo_get(cfile); > + > + queue_work(cifsoplockd_wq, &cfile->oplock_break); > +} > + > void cifs_done_oplock_break(struct cifsInodeInfo *cinode) > { > clear_bit(CIFS_INODE_PENDING_OPLOCK_BREAK, &cinode->flags); > diff --git a/fs/cifs/smb2misc.c b/fs/cifs/smb2misc.c > index 0e3570e40ff8..e311f58dc1c8 100644 > --- a/fs/cifs/smb2misc.c > +++ b/fs/cifs/smb2misc.c > @@ -555,7 +555,7 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp, > clear_bit(CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2, > &cinode->flags); > > - queue_work(cifsoplockd_wq, &cfile->oplock_break); > + cifs_queue_oplock_break(cfile); > kfree(lw); > return true; > } > @@ -712,8 +712,8 @@ smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server) > CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2, > &cinode->flags); > spin_unlock(&cfile->file_info_lock); > - queue_work(cifsoplockd_wq, > - &cfile->oplock_break); > + > + cifs_queue_oplock_break(cfile); > > spin_unlock(&tcon->open_file_lock); > spin_unlock(&cifs_tcp_ses_lock); > -- > 2.16.4 > Looks good, thanks! Reviewed-by: Pavel Shilovsky <pshilov@xxxxxxxxxxxxx> -- Best regards, Pavel Shilovsky