OSDN Git Service

NFS: Fix memory leaks and corruption in readdir
[sagit-ice-cold/kernel_xiaomi_msm8998.git] / fs / nfs / dir.c
index ce5a218..5fae075 100644 (file)
@@ -169,6 +169,17 @@ typedef struct {
        unsigned int    eof:1;
 } nfs_readdir_descriptor_t;
 
+static
+void nfs_readdir_init_array(struct page *page)
+{
+       struct nfs_cache_array *array;
+
+       array = kmap_atomic(page);
+       memset(array, 0, sizeof(struct nfs_cache_array));
+       array->eof_index = -1;
+       kunmap_atomic(array);
+}
+
 /*
  * The caller is responsible for calling nfs_readdir_release_array(page)
  */
@@ -202,6 +213,7 @@ void nfs_readdir_clear_array(struct page *page)
        array = kmap_atomic(page);
        for (i = 0; i < array->size; i++)
                kfree(array->array[i].string.name);
+       array->size = 0;
        kunmap_atomic(array);
 }
 
@@ -377,7 +389,7 @@ int nfs_readdir_xdr_filler(struct page **pages, nfs_readdir_descriptor_t *desc,
  again:
        timestamp = jiffies;
        gencount = nfs_inc_attr_generation_counter();
-       error = NFS_PROTO(inode)->readdir(file->f_path.dentry, cred, entry->cookie, pages,
+       error = NFS_PROTO(inode)->readdir(file_dentry(file), cred, entry->cookie, pages,
                                          NFS_SERVER(inode)->dtsize, desc->plus);
        if (error < 0) {
                /* We requested READDIRPLUS, but the server doesn't grok it */
@@ -462,7 +474,7 @@ void nfs_force_use_readdirplus(struct inode *dir)
 {
        if (!list_empty(&NFS_I(dir)->open_files)) {
                nfs_advise_use_readdirplus(dir);
-               nfs_zap_mapping(dir, dir->i_mapping);
+               invalidate_mapping_pages(dir->i_mapping, 0, -1);
        }
 }
 
@@ -560,7 +572,7 @@ int nfs_readdir_page_filler(nfs_readdir_descriptor_t *desc, struct nfs_entry *en
                count++;
 
                if (desc->plus != 0)
-                       nfs_prime_dcache(desc->file->f_path.dentry, entry);
+                       nfs_prime_dcache(file_dentry(desc->file), entry);
 
                status = nfs_readdir_add_to_array(entry, page);
                if (status != 0)
@@ -622,6 +634,8 @@ int nfs_readdir_xdr_to_array(nfs_readdir_descriptor_t *desc, struct page *page,
        int status = -ENOMEM;
        unsigned int array_size = ARRAY_SIZE(pages);
 
+       nfs_readdir_init_array(page);
+
        entry.prev_cookie = 0;
        entry.cookie = desc->last_cookie;
        entry.eof = 0;
@@ -642,8 +656,8 @@ int nfs_readdir_xdr_to_array(nfs_readdir_descriptor_t *desc, struct page *page,
                status = PTR_ERR(array);
                goto out_label_free;
        }
-       memset(array, 0, sizeof(struct nfs_cache_array));
-       array->eof_index = -1;
+
+       array = kmap(page);
 
        status = nfs_readdir_alloc_pages(pages, array_size);
        if (status < 0)
@@ -698,6 +712,7 @@ int nfs_readdir_filler(nfs_readdir_descriptor_t *desc, struct page* page)
        unlock_page(page);
        return 0;
  error:
+       nfs_readdir_clear_array(page);
        unlock_page(page);
        return ret;
 }
@@ -847,24 +862,13 @@ int uncached_readdir(nfs_readdir_descriptor_t *desc)
        goto out;
 }
 
-static bool nfs_dir_mapping_need_revalidate(struct inode *dir)
-{
-       struct nfs_inode *nfsi = NFS_I(dir);
-
-       if (nfs_attribute_cache_expired(dir))
-               return true;
-       if (nfsi->cache_validity & NFS_INO_INVALID_DATA)
-               return true;
-       return false;
-}
-
 /* The file offset position represents the dirent entry number.  A
    last cookie cache takes care of the common case of reading the
    whole directory.
  */
 static int nfs_readdir(struct file *file, struct dir_context *ctx)
 {
-       struct dentry   *dentry = file->f_path.dentry;
+       struct dentry   *dentry = file_dentry(file);
        struct inode    *inode = d_inode(dentry);
        nfs_readdir_descriptor_t my_desc,
                        *desc = &my_desc;
@@ -890,7 +894,7 @@ static int nfs_readdir(struct file *file, struct dir_context *ctx)
        desc->plus = nfs_use_readdirplus(inode, ctx) ? 1 : 0;
 
        nfs_block_sillyrename(dentry);
-       if (ctx->pos == 0 || nfs_dir_mapping_need_revalidate(inode))
+       if (ctx->pos == 0 || nfs_attribute_cache_expired(inode))
                res = nfs_revalidate_mapping(inode, file->f_mapping);
        if (res < 0)
                goto out;
@@ -1146,11 +1150,13 @@ static int nfs_lookup_revalidate(struct dentry *dentry, unsigned int flags)
        /* Force a full look up iff the parent directory has changed */
        if (!nfs_is_exclusive_create(dir, flags) &&
            nfs_check_verifier(dir, dentry, flags & LOOKUP_RCU)) {
-
-               if (nfs_lookup_verify_inode(inode, flags)) {
+               error = nfs_lookup_verify_inode(inode, flags);
+               if (error) {
                        if (flags & LOOKUP_RCU)
                                return -ECHILD;
-                       goto out_zap_parent;
+                       if (error == -ESTALE)
+                               goto out_zap_parent;
+                       goto out_error;
                }
                goto out_valid;
        }
@@ -1174,8 +1180,10 @@ static int nfs_lookup_revalidate(struct dentry *dentry, unsigned int flags)
        trace_nfs_lookup_revalidate_enter(dir, dentry, flags);
        error = NFS_PROTO(dir)->lookup(dir, &dentry->d_name, fhandle, fattr, label);
        trace_nfs_lookup_revalidate_exit(dir, dentry, flags, error);
-       if (error)
+       if (error == -ESTALE || error == -ENOENT)
                goto out_bad;
+       if (error)
+               goto out_error;
        if (nfs_compare_fh(NFS_FH(inode), fhandle))
                goto out_bad;
        if ((error = nfs_refresh_inode(inode, fattr)) != 0)
@@ -1267,7 +1275,7 @@ static int nfs_weak_revalidate(struct dentry *dentry, unsigned int flags)
                return 0;
        }
 
-       error = nfs_revalidate_inode(NFS_SERVER(inode), inode);
+       error = nfs_lookup_verify_inode(inode, flags);
        dfprintk(LOOKUPCACHE, "NFS: %s: inode %lu is %s\n",
                        __func__, inode->i_ino, error ? "invalid" : "valid");
        return !error;
@@ -1427,6 +1435,7 @@ static int nfs4_lookup_revalidate(struct dentry *, unsigned int);
 
 const struct dentry_operations nfs4_dentry_operations = {
        .d_revalidate   = nfs4_lookup_revalidate,
+       .d_weak_revalidate      = nfs_weak_revalidate,
        .d_delete       = nfs_dentry_delete,
        .d_iput         = nfs_dentry_iput,
        .d_automount    = nfs_d_automount,
@@ -1531,9 +1540,9 @@ int nfs_atomic_open(struct inode *dir, struct dentry *dentry,
                err = PTR_ERR(inode);
                trace_nfs_atomic_open_exit(dir, ctx, open_flags, err);
                put_nfs_open_context(ctx);
+               d_drop(dentry);
                switch (err) {
                case -ENOENT:
-                       d_drop(dentry);
                        d_add(dentry, NULL);
                        nfs_set_verifier(dentry, nfs_save_change_attribute(dir));
                        break;
@@ -2057,7 +2066,7 @@ out:
                if (new_inode != NULL)
                        nfs_drop_nlink(new_inode);
                d_move(old_dentry, new_dentry);
-               nfs_set_verifier(new_dentry,
+               nfs_set_verifier(old_dentry,
                                        nfs_save_change_attribute(new_dir));
        } else if (error == -ENOENT)
                nfs_dentry_handle_enoent(old_dentry);
@@ -2432,6 +2441,20 @@ int nfs_may_open(struct inode *inode, struct rpc_cred *cred, int openflags)
 }
 EXPORT_SYMBOL_GPL(nfs_may_open);
 
+static int nfs_execute_ok(struct inode *inode, int mask)
+{
+       struct nfs_server *server = NFS_SERVER(inode);
+       int ret;
+
+       if (mask & MAY_NOT_BLOCK)
+               ret = nfs_revalidate_inode_rcu(server, inode);
+       else
+               ret = nfs_revalidate_inode(server, inode);
+       if (ret == 0 && !execute_ok(inode))
+               ret = -EACCES;
+       return ret;
+}
+
 int nfs_permission(struct inode *inode, int mask)
 {
        struct rpc_cred *cred;
@@ -2449,6 +2472,9 @@ int nfs_permission(struct inode *inode, int mask)
                case S_IFLNK:
                        goto out;
                case S_IFREG:
+                       if ((mask & MAY_OPEN) &&
+                          nfs_server_capable(inode, NFS_CAP_ATOMIC_OPEN))
+                               return 0;
                        break;
                case S_IFDIR:
                        /*
@@ -2481,8 +2507,8 @@ force_lookup:
                        res = PTR_ERR(cred);
        }
 out:
-       if (!res && (mask & MAY_EXEC) && !execute_ok(inode))
-               res = -EACCES;
+       if (!res && (mask & MAY_EXEC))
+               res = nfs_execute_ok(inode, mask);
 
        dfprintk(VFS, "NFS: permission(%s/%lu), mask=0x%x, res=%d\n",
                inode->i_sb->s_id, inode->i_ino, mask, res);