nfs: add missing rcu-walk check
[pandora-kernel.git] / fs / nfs / dir.c
index f0a384e..a0d8320 100644 (file)
@@ -57,7 +57,7 @@ static int nfs_rename(struct inode *, struct dentry *,
                      struct inode *, struct dentry *);
 static int nfs_fsync_dir(struct file *, int);
 static loff_t nfs_llseek_dir(struct file *, loff_t, int);
-static int nfs_readdir_clear_array(struct page*, gfp_t);
+static void nfs_readdir_clear_array(struct page*);
 
 const struct file_operations nfs_dir_operations = {
        .llseek         = nfs_llseek_dir,
@@ -83,8 +83,8 @@ const struct inode_operations nfs_dir_inode_operations = {
        .setattr        = nfs_setattr,
 };
 
-const struct address_space_operations nfs_dir_addr_space_ops = {
-       .releasepage = nfs_readdir_clear_array,
+const struct address_space_operations nfs_dir_aops = {
+       .freepage = nfs_readdir_clear_array,
 };
 
 #ifdef CONFIG_NFS_V3
@@ -178,6 +178,7 @@ typedef struct {
        struct page     *page;
        unsigned long   page_index;
        u64             *dir_cookie;
+       u64             last_cookie;
        loff_t          current_index;
        decode_dirent_t decode;
 
@@ -213,17 +214,15 @@ void nfs_readdir_release_array(struct page *page)
  * we are freeing strings created by nfs_add_to_readdir_array()
  */
 static
-int nfs_readdir_clear_array(struct page *page, gfp_t mask)
+void nfs_readdir_clear_array(struct page *page)
 {
-       struct nfs_cache_array *array = nfs_readdir_get_array(page);
+       struct nfs_cache_array *array;
        int i;
 
-       if (IS_ERR(array))
-               return PTR_ERR(array);
+       array = kmap_atomic(page, KM_USER0);
        for (i = 0; i < array->size; i++)
                kfree(array->array[i].string.name);
-       nfs_readdir_release_array(page);
-       return 0;
+       kunmap_atomic(array, KM_USER0);
 }
 
 /*
@@ -272,7 +271,7 @@ int nfs_readdir_add_to_array(struct nfs_entry *entry, struct page *page)
                goto out;
        array->last_cookie = entry->cookie;
        array->size++;
-       if (entry->eof == 1)
+       if (entry->eof != 0)
                array->eof_index = array->size;
 out:
        nfs_readdir_release_array(page);
@@ -312,15 +311,14 @@ int nfs_readdir_search_for_cookie(struct nfs_cache_array *array, nfs_readdir_des
        for (i = 0; i < array->size; i++) {
                if (array->array[i].cookie == *desc->dir_cookie) {
                        desc->cache_entry_index = i;
-                       status = 0;
-                       goto out;
+                       return 0;
                }
        }
-       if (i == array->eof_index) {
-               desc->eof = 1;
+       if (array->eof_index >= 0) {
                status = -EBADCOOKIE;
+               if (*desc->dir_cookie == array->last_cookie)
+                       desc->eof = 1;
        }
-out:
        return status;
 }
 
@@ -328,10 +326,7 @@ static
 int nfs_readdir_search_array(nfs_readdir_descriptor_t *desc)
 {
        struct nfs_cache_array *array;
-       int status = -EBADCOOKIE;
-
-       if (desc->dir_cookie == NULL)
-               goto out;
+       int status;
 
        array = nfs_readdir_get_array(desc->page);
        if (IS_ERR(array)) {
@@ -344,6 +339,10 @@ int nfs_readdir_search_array(nfs_readdir_descriptor_t *desc)
        else
                status = nfs_readdir_search_for_cookie(array, desc);
 
+       if (status == -EAGAIN) {
+               desc->last_cookie = array->last_cookie;
+               desc->page_index++;
+       }
        nfs_readdir_release_array(desc->page);
 out:
        return status;
@@ -439,7 +438,7 @@ void nfs_prime_dcache(struct dentry *parent, struct nfs_entry *entry)
        if (dentry == NULL)
                return;
 
-       dentry->d_op = NFS_PROTO(dir)->dentry_ops;
+       d_set_d_op(dentry, NFS_PROTO(dir)->dentry_ops);
        inode = nfs_fhget(dentry->d_sb, entry->fh, entry->fattr);
        if (IS_ERR(inode))
                goto out;
@@ -490,7 +489,7 @@ int nfs_readdir_page_filler(nfs_readdir_descriptor_t *desc, struct nfs_entry *en
 
                count++;
 
-               if (desc->plus == 1)
+               if (desc->plus != 0)
                        nfs_prime_dcache(desc->file->f_path.dentry, entry);
 
                status = nfs_readdir_add_to_array(entry, page);
@@ -498,7 +497,7 @@ int nfs_readdir_page_filler(nfs_readdir_descriptor_t *desc, struct nfs_entry *en
                        break;
        } while (!entry->eof);
 
-       if (count == 0 || (status == -EBADCOOKIE && entry->eof == 1)) {
+       if (count == 0 || (status == -EBADCOOKIE && entry->eof != 0)) {
                array = nfs_readdir_get_array(page);
                if (!IS_ERR(array)) {
                        array->eof_index = array->size;
@@ -563,7 +562,7 @@ int nfs_readdir_xdr_to_array(nfs_readdir_descriptor_t *desc, struct page *page,
        unsigned int array_size = ARRAY_SIZE(pages);
 
        entry.prev_cookie = 0;
-       entry.cookie = *desc->dir_cookie;
+       entry.cookie = desc->last_cookie;
        entry.eof = 0;
        entry.fh = nfs_alloc_fhandle();
        entry.fattr = nfs_alloc_fattr();
@@ -636,6 +635,8 @@ 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);
        page_cache_release(desc->page);
        desc->page = NULL;
 }
@@ -660,9 +661,8 @@ int find_cache_page(nfs_readdir_descriptor_t *desc)
                return PTR_ERR(desc->page);
 
        res = nfs_readdir_search_array(desc);
-       if (res == 0)
-               return 0;
-       cache_page_release(desc);
+       if (res != 0)
+               cache_page_release(desc);
        return res;
 }
 
@@ -672,22 +672,16 @@ int readdir_search_pagecache(nfs_readdir_descriptor_t *desc)
 {
        int res;
 
-       if (desc->page_index == 0)
+       if (desc->page_index == 0) {
                desc->current_index = 0;
-       while (1) {
-               res = find_cache_page(desc);
-               if (res != -EAGAIN)
-                       break;
-               desc->page_index++;
+               desc->last_cookie = 0;
        }
+       do {
+               res = find_cache_page(desc);
+       } while (res == -EAGAIN);
        return res;
 }
 
-static inline unsigned int dt_type(struct inode *inode)
-{
-       return (inode->i_mode >> 12) & 15;
-}
-
 /*
  * Once we've found the start of the dirent within a page: fill 'er up...
  */
@@ -717,13 +711,12 @@ int nfs_do_filldir(nfs_readdir_descriptor_t *desc, void *dirent,
                        break;
                }
                file->f_pos++;
-               desc->cache_entry_index = i;
                if (i < (array->size-1))
                        *desc->dir_cookie = array->array[i+1].cookie;
                else
                        *desc->dir_cookie = array->last_cookie;
        }
-       if (i == array->eof_index)
+       if (array->eof_index >= 0)
                desc->eof = 1;
 
        nfs_readdir_release_array(desc->page);
@@ -764,6 +757,7 @@ int uncached_readdir(nfs_readdir_descriptor_t *desc, void *dirent,
        }
 
        desc->page_index = 0;
+       desc->last_cookie = *desc->dir_cookie;
        desc->page = page;
 
        status = nfs_readdir_xdr_to_array(desc, page, inode);
@@ -791,7 +785,7 @@ static int nfs_readdir(struct file *filp, void *dirent, filldir_t filldir)
        struct inode    *inode = dentry->d_inode;
        nfs_readdir_descriptor_t my_desc,
                        *desc = &my_desc;
-       int res = -ENOMEM;
+       int res;
 
        dfprintk(FILE, "NFS: readdir(%s/%s) starting at cookie %llu\n",
                        dentry->d_parent->d_name.name, dentry->d_name.name,
@@ -816,7 +810,7 @@ static int nfs_readdir(struct file *filp, void *dirent, filldir_t filldir)
        if (res < 0)
                goto out;
 
-       while (desc->eof != 1) {
+       do {
                res = readdir_search_pagecache(desc);
 
                if (res == -EBADCOOKIE) {
@@ -844,7 +838,7 @@ static int nfs_readdir(struct file *filp, void *dirent, filldir_t filldir)
                res = nfs_do_filldir(desc, dirent, filldir);
                if (res < 0)
                        break;
-       }
+       } while (!desc->eof);
 out:
        nfs_unblock_sillyrename(dentry);
        if (res > 0)
@@ -944,7 +938,8 @@ static int nfs_check_verifier(struct inode *dir, struct dentry *dentry)
  * component of the path.
  * We check for this using LOOKUP_CONTINUE and LOOKUP_PARENT.
  */
-static inline unsigned int nfs_lookup_check_intent(struct nameidata *nd, unsigned int mask)
+static inline unsigned int nfs_lookup_check_intent(struct nameidata *nd,
+                                               unsigned int mask)
 {
        if (nd->flags & (LOOKUP_CONTINUE|LOOKUP_PARENT))
                return 0;
@@ -1024,7 +1019,7 @@ int nfs_neg_need_reval(struct inode *dir, struct dentry *dentry,
  * If the parent directory is seen to have changed, we throw out the
  * cached dentry and do a new lookup.
  */
-static int nfs_lookup_revalidate(struct dentry * dentry, struct nameidata *nd)
+static int nfs_lookup_revalidate(struct dentry *dentry, struct nameidata *nd)
 {
        struct inode *dir;
        struct inode *inode;
@@ -1033,6 +1028,9 @@ static int nfs_lookup_revalidate(struct dentry * dentry, struct nameidata *nd)
        struct nfs_fattr *fattr = NULL;
        int error;
 
+       if (nd->flags & LOOKUP_RCU)
+               return -ECHILD;
+
        parent = dget_parent(dentry);
        dir = parent->d_inode;
        nfs_inc_stats(dir, NFSIOS_DENTRYREVALIDATE);
@@ -1123,7 +1121,7 @@ out_error:
 /*
  * This is called from dput() when d_count is going to 0.
  */
-static int nfs_dentry_delete(struct dentry *dentry)
+static int nfs_dentry_delete(const struct dentry *dentry)
 {
        dfprintk(VFS, "NFS: dentry_delete(%s/%s, %x)\n",
                dentry->d_parent->d_name.name, dentry->d_name.name,
@@ -1194,7 +1192,7 @@ static struct dentry *nfs_lookup(struct inode *dir, struct dentry * dentry, stru
        if (dentry->d_name.len > NFS_SERVER(dir)->namelen)
                goto out;
 
-       dentry->d_op = NFS_PROTO(dir)->dentry_ops;
+       d_set_d_op(dentry, NFS_PROTO(dir)->dentry_ops);
 
        /*
         * If we're doing an exclusive create, optimize away the lookup
@@ -1339,7 +1337,7 @@ static struct dentry *nfs_atomic_lookup(struct inode *dir, struct dentry *dentry
                res = ERR_PTR(-ENAMETOOLONG);
                goto out;
        }
-       dentry->d_op = NFS_PROTO(dir)->dentry_ops;
+       d_set_d_op(dentry, NFS_PROTO(dir)->dentry_ops);
 
        /* Let vfs_create() deal with O_EXCL. Instantiate, but don't hash
         * the dentry. */
@@ -1412,11 +1410,15 @@ no_open:
 static int nfs_open_revalidate(struct dentry *dentry, struct nameidata *nd)
 {
        struct dentry *parent = NULL;
-       struct inode *inode = dentry->d_inode;
+       struct inode *inode;
        struct inode *dir;
        struct nfs_open_context *ctx;
        int openflags, ret = 0;
 
+       if (nd->flags & LOOKUP_RCU)
+               return -ECHILD;
+
+       inode = dentry->d_inode;
        if (!is_atomic_open(nd) || d_mountpoint(dentry))
                goto no_open;
 
@@ -1724,11 +1726,9 @@ static int nfs_unlink(struct inode *dir, struct dentry *dentry)
        dfprintk(VFS, "NFS: unlink(%s/%ld, %s)\n", dir->i_sb->s_id,
                dir->i_ino, dentry->d_name.name);
 
-       spin_lock(&dcache_lock);
        spin_lock(&dentry->d_lock);
-       if (atomic_read(&dentry->d_count) > 1) {
+       if (dentry->d_count > 1) {
                spin_unlock(&dentry->d_lock);
-               spin_unlock(&dcache_lock);
                /* Start asynchronous writeout of the inode */
                write_inode_now(dentry->d_inode, 0);
                error = nfs_sillyrename(dir, dentry);
@@ -1739,7 +1739,6 @@ static int nfs_unlink(struct inode *dir, struct dentry *dentry)
                need_rehash = 1;
        }
        spin_unlock(&dentry->d_lock);
-       spin_unlock(&dcache_lock);
        error = nfs_safe_remove(dentry);
        if (!error || error == -ENOENT) {
                nfs_set_verifier(dentry, nfs_save_change_attribute(dir));
@@ -1874,7 +1873,7 @@ static int nfs_rename(struct inode *old_dir, struct dentry *old_dentry,
        dfprintk(VFS, "NFS: rename(%s/%s -> %s/%s, ct=%d)\n",
                 old_dentry->d_parent->d_name.name, old_dentry->d_name.name,
                 new_dentry->d_parent->d_name.name, new_dentry->d_name.name,
-                atomic_read(&new_dentry->d_count));
+                new_dentry->d_count);
 
        /*
         * For non-directories, check whether the target is busy and if so,
@@ -1892,7 +1891,7 @@ static int nfs_rename(struct inode *old_dir, struct dentry *old_dentry,
                        rehash = new_dentry;
                }
 
-               if (atomic_read(&new_dentry->d_count) > 2) {
+               if (new_dentry->d_count > 2) {
                        int err;
 
                        /* copy the target dentry's name */
@@ -2194,11 +2193,14 @@ int nfs_may_open(struct inode *inode, struct rpc_cred *cred, int openflags)
        return nfs_do_access(inode, cred, nfs_open_permission_mask(openflags));
 }
 
-int nfs_permission(struct inode *inode, int mask)
+int nfs_permission(struct inode *inode, int mask, unsigned int flags)
 {
        struct rpc_cred *cred;
        int res = 0;
 
+       if (flags & IPERM_FLAG_RCU)
+               return -ECHILD;
+
        nfs_inc_stats(inode, NFSIOS_VFSACCESS);
 
        if ((mask & (MAY_READ | MAY_WRITE | MAY_EXEC)) == 0)
@@ -2246,7 +2248,7 @@ out:
 out_notsup:
        res = nfs_revalidate_inode(NFS_SERVER(inode), inode);
        if (res == 0)
-               res = generic_permission(inode, mask, NULL);
+               res = generic_permission(inode, mask, flags, NULL);
        goto out;
 }