summaryrefslogtreecommitdiffstats
path: root/fs/nfs/dir.c
diff options
context:
space:
mode:
authorAl Viro <viro@zeniv.linux.org.uk>2016-04-29 01:52:56 +0200
committerAl Viro <viro@zeniv.linux.org.uk>2016-05-03 01:51:53 +0200
commit9ac3d3e8460e3fa6f3a9a39c2049904005016db6 (patch)
treec7f2b2a8784c638eaf9d39d6a01c5f6ad841a276 /fs/nfs/dir.c
parentlookup_open(): lock the parent shared unless O_CREAT is given (diff)
downloadlinux-9ac3d3e8460e3fa6f3a9a39c2049904005016db6.tar.xz
linux-9ac3d3e8460e3fa6f3a9a39c2049904005016db6.zip
nfs: switch to ->iterate_shared()
aside of the usual care about seeding dcache from readdir, we need to be careful about the pagecache evictions here. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Diffstat (limited to 'fs/nfs/dir.c')
-rw-r--r--fs/nfs/dir.c71
1 files changed, 43 insertions, 28 deletions
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index 33eb81738d03..cd3c754e3f89 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -57,7 +57,7 @@ static void nfs_readdir_clear_array(struct page*);
const struct file_operations nfs_dir_operations = {
.llseek = nfs_llseek_dir,
.read = generic_read_dir,
- .iterate = nfs_readdir,
+ .iterate_shared = nfs_readdir,
.open = nfs_opendir,
.release = nfs_closedir,
.fsync = nfs_fsync_dir,
@@ -145,6 +145,7 @@ struct nfs_cache_array_entry {
};
struct nfs_cache_array {
+ atomic_t refcount;
int size;
int eof_index;
u64 last_cookie;
@@ -200,11 +201,20 @@ void nfs_readdir_clear_array(struct page *page)
int i;
array = kmap_atomic(page);
- for (i = 0; i < array->size; i++)
- kfree(array->array[i].string.name);
+ if (atomic_dec_and_test(&array->refcount))
+ for (i = 0; i < array->size; i++)
+ kfree(array->array[i].string.name);
kunmap_atomic(array);
}
+static bool grab_page(struct page *page)
+{
+ struct nfs_cache_array *array = kmap_atomic(page);
+ bool res = atomic_inc_not_zero(&array->refcount);
+ kunmap_atomic(array);
+ return res;
+}
+
/*
* the caller is responsible for freeing qstr.name
* when called by nfs_readdir_add_to_array, the strings will be freed in
@@ -470,6 +480,7 @@ static
void nfs_prime_dcache(struct dentry *parent, struct nfs_entry *entry)
{
struct qstr filename = QSTR_INIT(entry->name, entry->len);
+ DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq);
struct dentry *dentry;
struct dentry *alias;
struct inode *dir = d_inode(parent);
@@ -489,7 +500,13 @@ void nfs_prime_dcache(struct dentry *parent, struct nfs_entry *entry)
filename.hash = full_name_hash(filename.name, filename.len);
dentry = d_lookup(parent, &filename);
- if (dentry != NULL) {
+again:
+ if (!dentry) {
+ dentry = d_alloc_parallel(parent, &filename, &wq);
+ if (IS_ERR(dentry))
+ return;
+ }
+ if (!d_in_lookup(dentry)) {
/* Is there a mountpoint here? If so, just exit */
if (!nfs_fsid_equal(&NFS_SB(dentry->d_sb)->fsid,
&entry->fattr->fsid))
@@ -503,26 +520,21 @@ void nfs_prime_dcache(struct dentry *parent, struct nfs_entry *entry)
} else {
d_invalidate(dentry);
dput(dentry);
+ dentry = NULL;
+ goto again;
}
}
- dentry = d_alloc(parent, &filename);
- if (dentry == NULL)
- return;
-
inode = nfs_fhget(dentry->d_sb, entry->fh, entry->fattr, entry->label);
- if (IS_ERR(inode))
- goto out;
-
alias = d_splice_alias(inode, dentry);
- if (IS_ERR(alias))
- goto out;
- else if (alias) {
- nfs_set_verifier(alias, nfs_save_change_attribute(dir));
- dput(alias);
- } else
- nfs_set_verifier(dentry, nfs_save_change_attribute(dir));
-
+ d_lookup_done(dentry);
+ if (alias) {
+ if (IS_ERR(alias))
+ goto out;
+ dput(dentry);
+ dentry = alias;
+ }
+ nfs_set_verifier(dentry, nfs_save_change_attribute(dir));
out:
dput(dentry);
}
@@ -643,6 +655,7 @@ int nfs_readdir_xdr_to_array(nfs_readdir_descriptor_t *desc, struct page *page,
goto out_label_free;
}
memset(array, 0, sizeof(struct nfs_cache_array));
+ atomic_set(&array->refcount, 1);
array->eof_index = -1;
status = nfs_readdir_alloc_pages(pages, array_size);
@@ -705,8 +718,7 @@ int nfs_readdir_filler(nfs_readdir_descriptor_t *desc, struct page* page)
static
void cache_page_release(nfs_readdir_descriptor_t *desc)
{
- if (!desc->page->mapping)
- nfs_readdir_clear_array(desc->page);
+ nfs_readdir_clear_array(desc->page);
put_page(desc->page);
desc->page = NULL;
}
@@ -714,8 +726,16 @@ void cache_page_release(nfs_readdir_descriptor_t *desc)
static
struct page *get_cache_page(nfs_readdir_descriptor_t *desc)
{
- return read_cache_page(file_inode(desc->file)->i_mapping,
+ struct page *page;
+
+ for (;;) {
+ page = read_cache_page(file_inode(desc->file)->i_mapping,
desc->page_index, (filler_t *)nfs_readdir_filler, desc);
+ if (IS_ERR(page) || grab_page(page))
+ break;
+ put_page(page);
+ }
+ return page;
}
/*
@@ -934,13 +954,11 @@ out:
static loff_t nfs_llseek_dir(struct file *filp, loff_t offset, int whence)
{
- struct inode *inode = file_inode(filp);
struct nfs_open_dir_context *dir_ctx = filp->private_data;
dfprintk(FILE, "NFS: llseek dir(%pD2, %lld, %d)\n",
filp, offset, whence);
- inode_lock(inode);
switch (whence) {
case 1:
offset += filp->f_pos;
@@ -948,16 +966,13 @@ static loff_t nfs_llseek_dir(struct file *filp, loff_t offset, int whence)
if (offset >= 0)
break;
default:
- offset = -EINVAL;
- goto out;
+ return -EINVAL;
}
if (offset != filp->f_pos) {
filp->f_pos = offset;
dir_ctx->dir_cookie = 0;
dir_ctx->duped = 0;
}
-out:
- inode_unlock(inode);
return offset;
}