summaryrefslogtreecommitdiffstats
path: root/fs/nfs/dir.c
diff options
context:
space:
mode:
authorTrond Myklebust <trond.myklebust@hammerspace.com>2022-03-21 22:27:13 -0400
committerTrond Myklebust <trond.myklebust@hammerspace.com>2022-03-22 09:14:39 -0400
commit648a4548d622c4ae965058db1a6b5b95c062789a (patch)
tree75b1b27ae64b689ebeddcb4093ef63786a5b3ca7 /fs/nfs/dir.c
parenta43bf604446414103b7535f38e739b65601c4fb2 (diff)
downloadlinux-648a4548d622c4ae965058db1a6b5b95c062789a.tar.gz
linux-648a4548d622c4ae965058db1a6b5b95c062789a.tar.bz2
linux-648a4548d622c4ae965058db1a6b5b95c062789a.zip
NFS: Don't deadlock when cookie hashes collide
In the very rare case where the readdir reply contains multiple cookies that map to the same hash value, we can end up deadlocking waiting for a page lock that we already hold. In this case we should fail the page lock by using grab_cache_page_nowait(). Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
Diffstat (limited to 'fs/nfs/dir.c')
-rw-r--r--fs/nfs/dir.c29
1 files changed, 18 insertions, 11 deletions
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index 7e12102b29e7..17986c0019d4 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -381,23 +381,28 @@ static void nfs_readdir_page_unlock_and_put(struct page *page)
put_page(page);
}
+static void nfs_readdir_page_init_and_validate(struct page *page, u64 cookie,
+ u64 change_attr)
+{
+ if (PageUptodate(page)) {
+ if (nfs_readdir_page_validate(page, cookie, change_attr))
+ return;
+ nfs_readdir_clear_array(page);
+ }
+ nfs_readdir_page_init_array(page, cookie, change_attr);
+ SetPageUptodate(page);
+}
+
static struct page *nfs_readdir_page_get_locked(struct address_space *mapping,
- u64 last_cookie,
- u64 change_attr)
+ u64 cookie, u64 change_attr)
{
- pgoff_t index = nfs_readdir_page_cookie_hash(last_cookie);
+ pgoff_t index = nfs_readdir_page_cookie_hash(cookie);
struct page *page;
page = grab_cache_page(mapping, index);
if (!page)
return NULL;
- if (PageUptodate(page)) {
- if (nfs_readdir_page_validate(page, last_cookie, change_attr))
- return page;
- nfs_readdir_clear_array(page);
- }
- nfs_readdir_page_init_array(page, last_cookie, change_attr);
- SetPageUptodate(page);
+ nfs_readdir_page_init_and_validate(page, cookie, change_attr);
return page;
}
@@ -435,11 +440,13 @@ static void nfs_readdir_page_set_eof(struct page *page)
static struct page *nfs_readdir_page_get_next(struct address_space *mapping,
u64 cookie, u64 change_attr)
{
+ pgoff_t index = nfs_readdir_page_cookie_hash(cookie);
struct page *page;
- page = nfs_readdir_page_get_locked(mapping, cookie, change_attr);
+ page = grab_cache_page_nowait(mapping, index);
if (!page)
return NULL;
+ nfs_readdir_page_init_and_validate(page, cookie, change_attr);
if (nfs_readdir_page_last_cookie(page) != cookie)
nfs_readdir_page_reinit_array(page, cookie, change_attr);
return page;