aboutsummaryrefslogtreecommitdiffstats
path: root/fs/cifs
diff options
context:
space:
mode:
authorAurelien Aptel <aaptel@suse.com>2019-03-29 05:49:12 -0400
committerSteve French <stfrench@microsoft.com>2019-04-16 10:38:38 -0400
commitb98749cac4a695f084a5ff076f4510b23e353ecd (patch)
treeb1b5aa35d4d1410bef00756fb2aacb125f5cb729 /fs/cifs
parente6d0fb7b34f264f72c33053558a360a6a734905e (diff)
CIFS: keep FileInfo handle live during oplock break
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. Also important fix to address the various reports of oops in smb2_push_mandatory_locks Signed-off-by: Aurelien Aptel <aaptel@suse.com> Signed-off-by: Steve French <stfrench@microsoft.com> Reviewed-by: Pavel Shilovsky <pshilov@microsoft.com> CC: Stable <stable@vger.kernel.org>
Diffstat (limited to 'fs/cifs')
-rw-r--r--fs/cifs/cifsglob.h2
-rw-r--r--fs/cifs/file.c30
-rw-r--r--fs/cifs/misc.c25
-rw-r--r--fs/cifs/smb2misc.c6
4 files changed, 53 insertions, 10 deletions
diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
index 5b18d4585740..585ad3207cb1 100644
--- a/fs/cifs/cifsglob.h
+++ b/fs/cifs/cifsglob.h
@@ -1333,6 +1333,7 @@ cifsFileInfo_get_locked(struct cifsFileInfo *cifs_file)
1333} 1333}
1334 1334
1335struct cifsFileInfo *cifsFileInfo_get(struct cifsFileInfo *cifs_file); 1335struct cifsFileInfo *cifsFileInfo_get(struct cifsFileInfo *cifs_file);
1336void _cifsFileInfo_put(struct cifsFileInfo *cifs_file, bool wait_oplock_hdlr);
1336void cifsFileInfo_put(struct cifsFileInfo *cifs_file); 1337void cifsFileInfo_put(struct cifsFileInfo *cifs_file);
1337 1338
1338#define CIFS_CACHE_READ_FLG 1 1339#define CIFS_CACHE_READ_FLG 1
@@ -1855,6 +1856,7 @@ GLOBAL_EXTERN spinlock_t gidsidlock;
1855#endif /* CONFIG_CIFS_ACL */ 1856#endif /* CONFIG_CIFS_ACL */
1856 1857
1857void cifs_oplock_break(struct work_struct *work); 1858void cifs_oplock_break(struct work_struct *work);
1859void cifs_queue_oplock_break(struct cifsFileInfo *cfile);
1858 1860
1859extern const struct slow_work_ops cifs_oplock_break_ops; 1861extern const struct slow_work_ops cifs_oplock_break_ops;
1860extern struct workqueue_struct *cifsiod_wq; 1862extern 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,13 +360,31 @@ cifsFileInfo_get(struct cifsFileInfo *cifs_file)
360 return cifs_file; 360 return cifs_file;
361} 361}
362 362
363/* 363/**
364 * Release a reference on the file private data. This may involve closing 364 * cifsFileInfo_put - release a reference of file priv data
365 * the filehandle out on the server. Must be called without holding 365 *
366 * tcon->open_file_lock and cifs_file->file_info_lock. 366 * Always potentially wait for oplock handler. See _cifsFileInfo_put().
367 */ 367 */
368void cifsFileInfo_put(struct cifsFileInfo *cifs_file) 368void cifsFileInfo_put(struct cifsFileInfo *cifs_file)
369{ 369{
370 _cifsFileInfo_put(cifs_file, true);
371}
372
373/**
374 * _cifsFileInfo_put - release a reference of file priv data
375 *
376 * This may involve closing the filehandle @cifs_file out on the
377 * server. Must be called without holding tcon->open_file_lock and
378 * cifs_file->file_info_lock.
379 *
380 * If @wait_for_oplock_handler is true and we are releasing the last
381 * reference, wait for any running oplock break handler of the file
382 * and cancel any pending one. If calling this function from the
383 * oplock break handler, you need to pass false.
384 *
385 */
386void _cifsFileInfo_put(struct cifsFileInfo *cifs_file, bool wait_oplock_handler)
387{
370 struct inode *inode = d_inode(cifs_file->dentry); 388 struct inode *inode = d_inode(cifs_file->dentry);
371 struct cifs_tcon *tcon = tlink_tcon(cifs_file->tlink); 389 struct cifs_tcon *tcon = tlink_tcon(cifs_file->tlink);
372 struct TCP_Server_Info *server = tcon->ses->server; 390 struct TCP_Server_Info *server = tcon->ses->server;
@@ -414,7 +432,8 @@ void cifsFileInfo_put(struct cifsFileInfo *cifs_file)
414 432
415 spin_unlock(&tcon->open_file_lock); 433 spin_unlock(&tcon->open_file_lock);
416 434
417 oplock_break_cancelled = cancel_work_sync(&cifs_file->oplock_break); 435 oplock_break_cancelled = wait_oplock_handler ?
436 cancel_work_sync(&cifs_file->oplock_break) : false;
418 437
419 if (!tcon->need_reconnect && !cifs_file->invalidHandle) { 438 if (!tcon->need_reconnect && !cifs_file->invalidHandle) {
420 struct TCP_Server_Info *server = tcon->ses->server; 439 struct TCP_Server_Info *server = tcon->ses->server;
@@ -4603,6 +4622,7 @@ void cifs_oplock_break(struct work_struct *work)
4603 cinode); 4622 cinode);
4604 cifs_dbg(FYI, "Oplock release rc = %d\n", rc); 4623 cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
4605 } 4624 }
4625 _cifsFileInfo_put(cfile, false /* do not wait for ourself */);
4606 cifs_done_oplock_break(cinode); 4626 cifs_done_oplock_break(cinode);
4607} 4627}
4608 4628
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)
501 CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2, 501 CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
502 &pCifsInode->flags); 502 &pCifsInode->flags);
503 503
504 queue_work(cifsoplockd_wq, 504 cifs_queue_oplock_break(netfile);
505 &netfile->oplock_break);
506 netfile->oplock_break_cancelled = false; 505 netfile->oplock_break_cancelled = false;
507 506
508 spin_unlock(&tcon->open_file_lock); 507 spin_unlock(&tcon->open_file_lock);
@@ -607,6 +606,28 @@ void cifs_put_writer(struct cifsInodeInfo *cinode)
607 spin_unlock(&cinode->writers_lock); 606 spin_unlock(&cinode->writers_lock);
608} 607}
609 608
609/**
610 * cifs_queue_oplock_break - queue the oplock break handler for cfile
611 *
612 * This function is called from the demultiplex thread when it
613 * receives an oplock break for @cfile.
614 *
615 * Assumes the tcon->open_file_lock is held.
616 * Assumes cfile->file_info_lock is NOT held.
617 */
618void cifs_queue_oplock_break(struct cifsFileInfo *cfile)
619{
620 /*
621 * Bump the handle refcount now while we hold the
622 * open_file_lock to enforce the validity of it for the oplock
623 * break handler. The matching put is done at the end of the
624 * handler.
625 */
626 cifsFileInfo_get(cfile);
627
628 queue_work(cifsoplockd_wq, &cfile->oplock_break);
629}
630
610void cifs_done_oplock_break(struct cifsInodeInfo *cinode) 631void cifs_done_oplock_break(struct cifsInodeInfo *cinode)
611{ 632{
612 clear_bit(CIFS_INODE_PENDING_OPLOCK_BREAK, &cinode->flags); 633 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,
555 clear_bit(CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2, 555 clear_bit(CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
556 &cinode->flags); 556 &cinode->flags);
557 557
558 queue_work(cifsoplockd_wq, &cfile->oplock_break); 558 cifs_queue_oplock_break(cfile);
559 kfree(lw); 559 kfree(lw);
560 return true; 560 return true;
561 } 561 }
@@ -712,8 +712,8 @@ smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server)
712 CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2, 712 CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
713 &cinode->flags); 713 &cinode->flags);
714 spin_unlock(&cfile->file_info_lock); 714 spin_unlock(&cfile->file_info_lock);
715 queue_work(cifsoplockd_wq, 715
716 &cfile->oplock_break); 716 cifs_queue_oplock_break(cfile);
717 717
718 spin_unlock(&tcon->open_file_lock); 718 spin_unlock(&tcon->open_file_lock);
719 spin_unlock(&cifs_tcp_ses_lock); 719 spin_unlock(&cifs_tcp_ses_lock);