Skip to content

Commit 3fa640d

Browse files
darkrain42Steve French
authored andcommitted
smb: During unmount, ensure all cached dir instances drop their dentry
The unmount process (cifs_kill_sb() calling close_all_cached_dirs()) can race with various cached directory operations, which ultimately results in dentries not being dropped and these kernel BUGs: BUG: Dentry ffff88814f37e358{i=1000000000080,n=/} still in use (2) [unmount of cifs cifs] VFS: Busy inodes after unmount of cifs (cifs) ------------[ cut here ]------------ kernel BUG at fs/super.c:661! This happens when a cfid is in the process of being cleaned up when, and has been removed from the cfids->entries list, including: - Receiving a lease break from the server - Server reconnection triggers invalidate_all_cached_dirs(), which removes all the cfids from the list - The laundromat thread decides to expire an old cfid. To solve these problems, dropping the dentry is done in queued work done in a newly-added cfid_put_wq workqueue, and close_all_cached_dirs() flushes that workqueue after it drops all the dentries of which it's aware. This is a global workqueue (rather than scoped to a mount), but the queued work is minimal. The final cleanup work for cleaning up a cfid is performed via work queued in the serverclose_wq workqueue; this is done separate from dropping the dentries so that close_all_cached_dirs() doesn't block on any server operations. Both of these queued works expect to invoked with a cfid reference and a tcon reference to avoid those objects from being freed while the work is ongoing. While we're here, add proper locking to close_all_cached_dirs(), and locking around the freeing of cfid->dentry. Fixes: ebe98f1 ("cifs: enable caching of directories for which a lease is held") Cc: stable@vger.kernel.org Signed-off-by: Paul Aurich <paul@darkrain42.org> Signed-off-by: Steve French <stfrench@microsoft.com>
1 parent 7967330 commit 3fa640d

File tree

6 files changed

+147
-36
lines changed

6 files changed

+147
-36
lines changed

fs/smb/client/cached_dir.c

Lines changed: 126 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ static void free_cached_dir(struct cached_fid *cfid);
1717
static void smb2_close_cached_fid(struct kref *ref);
1818
static void cfids_laundromat_worker(struct work_struct *work);
1919

20+
struct cached_dir_dentry {
21+
struct list_head entry;
22+
struct dentry *dentry;
23+
};
24+
2025
static struct cached_fid *find_or_create_cached_dir(struct cached_fids *cfids,
2126
const char *path,
2227
bool lookup_only,
@@ -472,7 +477,10 @@ void close_all_cached_dirs(struct cifs_sb_info *cifs_sb)
472477
struct cifs_tcon *tcon;
473478
struct tcon_link *tlink;
474479
struct cached_fids *cfids;
480+
struct cached_dir_dentry *tmp_list, *q;
481+
LIST_HEAD(entry);
475482

483+
spin_lock(&cifs_sb->tlink_tree_lock);
476484
for (node = rb_first(root); node; node = rb_next(node)) {
477485
tlink = rb_entry(node, struct tcon_link, tl_rbnode);
478486
tcon = tlink_tcon(tlink);
@@ -481,11 +489,30 @@ void close_all_cached_dirs(struct cifs_sb_info *cifs_sb)
481489
cfids = tcon->cfids;
482490
if (cfids == NULL)
483491
continue;
492+
spin_lock(&cfids->cfid_list_lock);
484493
list_for_each_entry(cfid, &cfids->entries, entry) {
485-
dput(cfid->dentry);
494+
tmp_list = kmalloc(sizeof(*tmp_list), GFP_ATOMIC);
495+
if (tmp_list == NULL)
496+
break;
497+
spin_lock(&cfid->fid_lock);
498+
tmp_list->dentry = cfid->dentry;
486499
cfid->dentry = NULL;
500+
spin_unlock(&cfid->fid_lock);
501+
502+
list_add_tail(&tmp_list->entry, &entry);
487503
}
504+
spin_unlock(&cfids->cfid_list_lock);
505+
}
506+
spin_unlock(&cifs_sb->tlink_tree_lock);
507+
508+
list_for_each_entry_safe(tmp_list, q, &entry, entry) {
509+
list_del(&tmp_list->entry);
510+
dput(tmp_list->dentry);
511+
kfree(tmp_list);
488512
}
513+
514+
/* Flush any pending work that will drop dentries */
515+
flush_workqueue(cfid_put_wq);
489516
}
490517

491518
/*
@@ -496,14 +523,18 @@ void invalidate_all_cached_dirs(struct cifs_tcon *tcon)
496523
{
497524
struct cached_fids *cfids = tcon->cfids;
498525
struct cached_fid *cfid, *q;
499-
LIST_HEAD(entry);
500526

501527
if (cfids == NULL)
502528
return;
503529

530+
/*
531+
* Mark all the cfids as closed, and move them to the cfids->dying list.
532+
* They'll be cleaned up later by cfids_invalidation_worker. Take
533+
* a reference to each cfid during this process.
534+
*/
504535
spin_lock(&cfids->cfid_list_lock);
505536
list_for_each_entry_safe(cfid, q, &cfids->entries, entry) {
506-
list_move(&cfid->entry, &entry);
537+
list_move(&cfid->entry, &cfids->dying);
507538
cfids->num_entries--;
508539
cfid->is_open = false;
509540
cfid->on_list = false;
@@ -516,26 +547,47 @@ void invalidate_all_cached_dirs(struct cifs_tcon *tcon)
516547
} else
517548
kref_get(&cfid->refcount);
518549
}
550+
/*
551+
* Queue dropping of the dentries once locks have been dropped
552+
*/
553+
if (!list_empty(&cfids->dying))
554+
queue_work(cfid_put_wq, &cfids->invalidation_work);
519555
spin_unlock(&cfids->cfid_list_lock);
520-
521-
list_for_each_entry_safe(cfid, q, &entry, entry) {
522-
list_del(&cfid->entry);
523-
cancel_work_sync(&cfid->lease_break);
524-
/*
525-
* Drop the ref-count from above, either the lease-ref (if there
526-
* was one) or the extra one acquired.
527-
*/
528-
kref_put(&cfid->refcount, smb2_close_cached_fid);
529-
}
530556
}
531557

532558
static void
533-
smb2_cached_lease_break(struct work_struct *work)
559+
cached_dir_offload_close(struct work_struct *work)
534560
{
535561
struct cached_fid *cfid = container_of(work,
536-
struct cached_fid, lease_break);
562+
struct cached_fid, close_work);
563+
struct cifs_tcon *tcon = cfid->tcon;
564+
565+
WARN_ON(cfid->on_list);
537566

538567
kref_put(&cfid->refcount, smb2_close_cached_fid);
568+
cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_cached_close);
569+
}
570+
571+
/*
572+
* Release the cached directory's dentry, and then queue work to drop cached
573+
* directory itself (closing on server if needed).
574+
*
575+
* Must be called with a reference to the cached_fid and a reference to the
576+
* tcon.
577+
*/
578+
static void cached_dir_put_work(struct work_struct *work)
579+
{
580+
struct cached_fid *cfid = container_of(work, struct cached_fid,
581+
put_work);
582+
struct dentry *dentry;
583+
584+
spin_lock(&cfid->fid_lock);
585+
dentry = cfid->dentry;
586+
cfid->dentry = NULL;
587+
spin_unlock(&cfid->fid_lock);
588+
589+
dput(dentry);
590+
queue_work(serverclose_wq, &cfid->close_work);
539591
}
540592

541593
int cached_dir_lease_break(struct cifs_tcon *tcon, __u8 lease_key[16])
@@ -562,8 +614,10 @@ int cached_dir_lease_break(struct cifs_tcon *tcon, __u8 lease_key[16])
562614
cfid->on_list = false;
563615
cfids->num_entries--;
564616

565-
queue_work(cifsiod_wq,
566-
&cfid->lease_break);
617+
++tcon->tc_count;
618+
trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count,
619+
netfs_trace_tcon_ref_get_cached_lease_break);
620+
queue_work(cfid_put_wq, &cfid->put_work);
567621
spin_unlock(&cfids->cfid_list_lock);
568622
return true;
569623
}
@@ -585,7 +639,8 @@ static struct cached_fid *init_cached_dir(const char *path)
585639
return NULL;
586640
}
587641

588-
INIT_WORK(&cfid->lease_break, smb2_cached_lease_break);
642+
INIT_WORK(&cfid->close_work, cached_dir_offload_close);
643+
INIT_WORK(&cfid->put_work, cached_dir_put_work);
589644
INIT_LIST_HEAD(&cfid->entry);
590645
INIT_LIST_HEAD(&cfid->dirents.entries);
591646
mutex_init(&cfid->dirents.de_mutex);
@@ -598,6 +653,9 @@ static void free_cached_dir(struct cached_fid *cfid)
598653
{
599654
struct cached_dirent *dirent, *q;
600655

656+
WARN_ON(work_pending(&cfid->close_work));
657+
WARN_ON(work_pending(&cfid->put_work));
658+
601659
dput(cfid->dentry);
602660
cfid->dentry = NULL;
603661

@@ -615,10 +673,30 @@ static void free_cached_dir(struct cached_fid *cfid)
615673
kfree(cfid);
616674
}
617675

676+
static void cfids_invalidation_worker(struct work_struct *work)
677+
{
678+
struct cached_fids *cfids = container_of(work, struct cached_fids,
679+
invalidation_work);
680+
struct cached_fid *cfid, *q;
681+
LIST_HEAD(entry);
682+
683+
spin_lock(&cfids->cfid_list_lock);
684+
/* move cfids->dying to the local list */
685+
list_cut_before(&entry, &cfids->dying, &cfids->dying);
686+
spin_unlock(&cfids->cfid_list_lock);
687+
688+
list_for_each_entry_safe(cfid, q, &entry, entry) {
689+
list_del(&cfid->entry);
690+
/* Drop the ref-count acquired in invalidate_all_cached_dirs */
691+
kref_put(&cfid->refcount, smb2_close_cached_fid);
692+
}
693+
}
694+
618695
static void cfids_laundromat_worker(struct work_struct *work)
619696
{
620697
struct cached_fids *cfids;
621698
struct cached_fid *cfid, *q;
699+
struct dentry *dentry;
622700
LIST_HEAD(entry);
623701

624702
cfids = container_of(work, struct cached_fids, laundromat_work.work);
@@ -644,18 +722,28 @@ static void cfids_laundromat_worker(struct work_struct *work)
644722

645723
list_for_each_entry_safe(cfid, q, &entry, entry) {
646724
list_del(&cfid->entry);
647-
/*
648-
* Cancel and wait for the work to finish in case we are racing
649-
* with it.
650-
*/
651-
cancel_work_sync(&cfid->lease_break);
652-
/*
653-
* Drop the ref-count from above, either the lease-ref (if there
654-
* was one) or the extra one acquired.
655-
*/
656-
kref_put(&cfid->refcount, smb2_close_cached_fid);
725+
726+
spin_lock(&cfid->fid_lock);
727+
dentry = cfid->dentry;
728+
cfid->dentry = NULL;
729+
spin_unlock(&cfid->fid_lock);
730+
731+
dput(dentry);
732+
if (cfid->is_open) {
733+
spin_lock(&cifs_tcp_ses_lock);
734+
++cfid->tcon->tc_count;
735+
trace_smb3_tcon_ref(cfid->tcon->debug_id, cfid->tcon->tc_count,
736+
netfs_trace_tcon_ref_get_cached_laundromat);
737+
spin_unlock(&cifs_tcp_ses_lock);
738+
queue_work(serverclose_wq, &cfid->close_work);
739+
} else
740+
/*
741+
* Drop the ref-count from above, either the lease-ref (if there
742+
* was one) or the extra one acquired.
743+
*/
744+
kref_put(&cfid->refcount, smb2_close_cached_fid);
657745
}
658-
queue_delayed_work(cifsiod_wq, &cfids->laundromat_work,
746+
queue_delayed_work(cfid_put_wq, &cfids->laundromat_work,
659747
dir_cache_timeout * HZ);
660748
}
661749

@@ -668,9 +756,11 @@ struct cached_fids *init_cached_dirs(void)
668756
return NULL;
669757
spin_lock_init(&cfids->cfid_list_lock);
670758
INIT_LIST_HEAD(&cfids->entries);
759+
INIT_LIST_HEAD(&cfids->dying);
671760

761+
INIT_WORK(&cfids->invalidation_work, cfids_invalidation_worker);
672762
INIT_DELAYED_WORK(&cfids->laundromat_work, cfids_laundromat_worker);
673-
queue_delayed_work(cifsiod_wq, &cfids->laundromat_work,
763+
queue_delayed_work(cfid_put_wq, &cfids->laundromat_work,
674764
dir_cache_timeout * HZ);
675765

676766
return cfids;
@@ -689,13 +779,19 @@ void free_cached_dirs(struct cached_fids *cfids)
689779
return;
690780

691781
cancel_delayed_work_sync(&cfids->laundromat_work);
782+
cancel_work_sync(&cfids->invalidation_work);
692783

693784
spin_lock(&cfids->cfid_list_lock);
694785
list_for_each_entry_safe(cfid, q, &cfids->entries, entry) {
695786
cfid->on_list = false;
696787
cfid->is_open = false;
697788
list_move(&cfid->entry, &entry);
698789
}
790+
list_for_each_entry_safe(cfid, q, &cfids->dying, entry) {
791+
cfid->on_list = false;
792+
cfid->is_open = false;
793+
list_move(&cfid->entry, &entry);
794+
}
699795
spin_unlock(&cfids->cfid_list_lock);
700796

701797
list_for_each_entry_safe(cfid, q, &entry, entry) {

fs/smb/client/cached_dir.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ struct cached_fid {
4444
spinlock_t fid_lock;
4545
struct cifs_tcon *tcon;
4646
struct dentry *dentry;
47-
struct work_struct lease_break;
47+
struct work_struct put_work;
48+
struct work_struct close_work;
4849
struct smb2_file_all_info file_all_info;
4950
struct cached_dirents dirents;
5051
};
@@ -53,10 +54,13 @@ struct cached_fid {
5354
struct cached_fids {
5455
/* Must be held when:
5556
* - accessing the cfids->entries list
57+
* - accessing the cfids->dying list
5658
*/
5759
spinlock_t cfid_list_lock;
5860
int num_entries;
5961
struct list_head entries;
62+
struct list_head dying;
63+
struct work_struct invalidation_work;
6064
struct delayed_work laundromat_work;
6165
};
6266

fs/smb/client/cifsfs.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ struct workqueue_struct *fileinfo_put_wq;
157157
struct workqueue_struct *cifsoplockd_wq;
158158
struct workqueue_struct *deferredclose_wq;
159159
struct workqueue_struct *serverclose_wq;
160+
struct workqueue_struct *cfid_put_wq;
160161
__u32 cifs_lock_secret;
161162

162163
/*
@@ -1920,9 +1921,16 @@ init_cifs(void)
19201921
goto out_destroy_deferredclose_wq;
19211922
}
19221923

1924+
cfid_put_wq = alloc_workqueue("cfid_put_wq",
1925+
WQ_FREEZABLE|WQ_MEM_RECLAIM, 0);
1926+
if (!cfid_put_wq) {
1927+
rc = -ENOMEM;
1928+
goto out_destroy_serverclose_wq;
1929+
}
1930+
19231931
rc = cifs_init_inodecache();
19241932
if (rc)
1925-
goto out_destroy_serverclose_wq;
1933+
goto out_destroy_cfid_put_wq;
19261934

19271935
rc = cifs_init_netfs();
19281936
if (rc)
@@ -1990,6 +1998,8 @@ init_cifs(void)
19901998
cifs_destroy_netfs();
19911999
out_destroy_inodecache:
19922000
cifs_destroy_inodecache();
2001+
out_destroy_cfid_put_wq:
2002+
destroy_workqueue(cfid_put_wq);
19932003
out_destroy_serverclose_wq:
19942004
destroy_workqueue(serverclose_wq);
19952005
out_destroy_deferredclose_wq:

fs/smb/client/cifsglob.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1991,7 +1991,7 @@ require use of the stronger protocol */
19911991
* cifsInodeInfo->lock_sem cifsInodeInfo->llist cifs_init_once
19921992
* ->can_cache_brlcks
19931993
* cifsInodeInfo->deferred_lock cifsInodeInfo->deferred_closes cifsInodeInfo_alloc
1994-
* cached_fid->fid_mutex cifs_tcon->crfid tcon_info_alloc
1994+
* cached_fids->cfid_list_lock cifs_tcon->cfids->entries init_cached_dirs
19951995
* cifsFileInfo->fh_mutex cifsFileInfo cifs_new_fileinfo
19961996
* cifsFileInfo->file_info_lock cifsFileInfo->count cifs_new_fileinfo
19971997
* ->invalidHandle initiate_cifs_search
@@ -2079,6 +2079,7 @@ extern struct workqueue_struct *fileinfo_put_wq;
20792079
extern struct workqueue_struct *cifsoplockd_wq;
20802080
extern struct workqueue_struct *deferredclose_wq;
20812081
extern struct workqueue_struct *serverclose_wq;
2082+
extern struct workqueue_struct *cfid_put_wq;
20822083
extern __u32 cifs_lock_secret;
20832084

20842085
extern mempool_t *cifs_sm_req_poolp;

fs/smb/client/inode.c

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2496,13 +2496,10 @@ cifs_dentry_needs_reval(struct dentry *dentry)
24962496
return true;
24972497

24982498
if (!open_cached_dir_by_dentry(tcon, dentry->d_parent, &cfid)) {
2499-
spin_lock(&cfid->fid_lock);
25002499
if (cfid->time && cifs_i->time > cfid->time) {
2501-
spin_unlock(&cfid->fid_lock);
25022500
close_cached_dir(cfid);
25032501
return false;
25042502
}
2505-
spin_unlock(&cfid->fid_lock);
25062503
close_cached_dir(cfid);
25072504
}
25082505
/*

fs/smb/client/trace.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
EM(netfs_trace_tcon_ref_free_ipc, "FRE Ipc ") \
4545
EM(netfs_trace_tcon_ref_free_ipc_fail, "FRE Ipc-F ") \
4646
EM(netfs_trace_tcon_ref_free_reconnect_server, "FRE Reconn") \
47+
EM(netfs_trace_tcon_ref_get_cached_laundromat, "GET Ch-Lau") \
48+
EM(netfs_trace_tcon_ref_get_cached_lease_break, "GET Ch-Lea") \
4749
EM(netfs_trace_tcon_ref_get_cancelled_close, "GET Cn-Cls") \
4850
EM(netfs_trace_tcon_ref_get_dfs_refer, "GET DfsRef") \
4951
EM(netfs_trace_tcon_ref_get_find, "GET Find ") \
@@ -52,6 +54,7 @@
5254
EM(netfs_trace_tcon_ref_new, "NEW ") \
5355
EM(netfs_trace_tcon_ref_new_ipc, "NEW Ipc ") \
5456
EM(netfs_trace_tcon_ref_new_reconnect_server, "NEW Reconn") \
57+
EM(netfs_trace_tcon_ref_put_cached_close, "PUT Ch-Cls") \
5558
EM(netfs_trace_tcon_ref_put_cancelled_close, "PUT Cn-Cls") \
5659
EM(netfs_trace_tcon_ref_put_cancelled_close_fid, "PUT Cn-Fid") \
5760
EM(netfs_trace_tcon_ref_put_cancelled_mid, "PUT Cn-Mid") \

0 commit comments

Comments
 (0)