]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - fs/nfs/dir.c
Merge branch 'perf-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel...
[linux.git] / fs / nfs / dir.c
index d95c2c94bd8764229669af8082451468d84881e8..193d6fb363b7434ae629fc48992812a198b47ec3 100644 (file)
@@ -58,7 +58,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,
@@ -155,6 +155,7 @@ typedef struct {
        loff_t          current_index;
        decode_dirent_t decode;
 
+       unsigned long   dir_verifier;
        unsigned long   timestamp;
        unsigned long   gencount;
        unsigned int    cache_entry_index;
@@ -198,7 +199,7 @@ static
 int nfs_readdir_make_qstr(struct qstr *string, const char *name, unsigned int len)
 {
        string->len = len;
-       string->name = kmemdup(name, len, GFP_KERNEL);
+       string->name = kmemdup_nul(name, len, GFP_KERNEL);
        if (string->name == NULL)
                return -ENOMEM;
        /*
@@ -353,6 +354,7 @@ int nfs_readdir_xdr_filler(struct page **pages, nfs_readdir_descriptor_t *desc,
  again:
        timestamp = jiffies;
        gencount = nfs_inc_attr_generation_counter();
+       desc->dir_verifier = nfs_save_change_attribute(inode);
        error = NFS_PROTO(inode)->readdir(file_dentry(file), cred, entry->cookie, pages,
                                          NFS_SERVER(inode)->dtsize, desc->plus);
        if (error < 0) {
@@ -449,18 +451,19 @@ void nfs_force_use_readdirplus(struct inode *dir)
        if (nfs_server_capable(dir, NFS_CAP_READDIRPLUS) &&
            !list_empty(&nfsi->open_files)) {
                set_bit(NFS_INO_ADVISE_RDPLUS, &nfsi->flags);
-               invalidate_mapping_pages(dir->i_mapping, 0, -1);
+               invalidate_mapping_pages(dir->i_mapping,
+                       nfsi->page_index + 1, -1);
        }
 }
 
 static
-void nfs_prime_dcache(struct dentry *parent, struct nfs_entry *entry)
+void nfs_prime_dcache(struct dentry *parent, struct nfs_entry *entry,
+               unsigned long dir_verifier)
 {
        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);
        struct inode *inode;
        int status;
 
@@ -499,7 +502,7 @@ void nfs_prime_dcache(struct dentry *parent, struct nfs_entry *entry)
                if (nfs_same_file(dentry, entry)) {
                        if (!entry->fh->size)
                                goto out;
-                       nfs_set_verifier(dentry, nfs_save_change_attribute(dir));
+                       nfs_set_verifier(dentry, dir_verifier);
                        status = nfs_refresh_inode(d_inode(dentry), entry->fattr);
                        if (!status)
                                nfs_setsecurity(d_inode(dentry), entry->fattr, entry->label);
@@ -525,7 +528,7 @@ void nfs_prime_dcache(struct dentry *parent, struct nfs_entry *entry)
                dput(dentry);
                dentry = alias;
        }
-       nfs_set_verifier(dentry, nfs_save_change_attribute(dir));
+       nfs_set_verifier(dentry, dir_verifier);
 out:
        dput(dentry);
 }
@@ -563,7 +566,8 @@ int nfs_readdir_page_filler(nfs_readdir_descriptor_t *desc, struct nfs_entry *en
                count++;
 
                if (desc->plus)
-                       nfs_prime_dcache(file_dentry(desc->file), entry);
+                       nfs_prime_dcache(file_dentry(desc->file), entry,
+                                       desc->dir_verifier);
 
                status = nfs_readdir_add_to_array(entry, page);
                if (status != 0)
@@ -720,6 +724,8 @@ struct page *get_cache_page(nfs_readdir_descriptor_t *desc)
 static
 int find_and_lock_cache_page(nfs_readdir_descriptor_t *desc)
 {
+       struct inode *inode = file_inode(desc->file);
+       struct nfs_inode *nfsi = NFS_I(inode);
        int res;
 
        desc->page = get_cache_page(desc);
@@ -731,8 +737,10 @@ int find_and_lock_cache_page(nfs_readdir_descriptor_t *desc)
        res = -EAGAIN;
        if (desc->page->mapping != NULL) {
                res = nfs_readdir_search_array(desc);
-               if (res == 0)
+               if (res == 0) {
+                       nfsi->page_index = desc->page_index;
                        return 0;
+               }
        }
        unlock_page(desc->page);
 error:
@@ -978,14 +986,113 @@ static int nfs_fsync_dir(struct file *filp, loff_t start, loff_t end,
  * full lookup on all child dentries of 'dir' whenever a change occurs
  * on the server that might have invalidated our dcache.
  *
+ * Note that we reserve bit '0' as a tag to let us know when a dentry
+ * was revalidated while holding a delegation on its inode.
+ *
  * The caller should be holding dir->i_lock
  */
 void nfs_force_lookup_revalidate(struct inode *dir)
 {
-       NFS_I(dir)->cache_change_attribute++;
+       NFS_I(dir)->cache_change_attribute += 2;
 }
 EXPORT_SYMBOL_GPL(nfs_force_lookup_revalidate);
 
+/**
+ * nfs_verify_change_attribute - Detects NFS remote directory changes
+ * @dir: pointer to parent directory inode
+ * @verf: previously saved change attribute
+ *
+ * Return "false" if the verifiers doesn't match the change attribute.
+ * This would usually indicate that the directory contents have changed on
+ * the server, and that any dentries need revalidating.
+ */
+static bool nfs_verify_change_attribute(struct inode *dir, unsigned long verf)
+{
+       return (verf & ~1UL) == nfs_save_change_attribute(dir);
+}
+
+static void nfs_set_verifier_delegated(unsigned long *verf)
+{
+       *verf |= 1UL;
+}
+
+#if IS_ENABLED(CONFIG_NFS_V4)
+static void nfs_unset_verifier_delegated(unsigned long *verf)
+{
+       *verf &= ~1UL;
+}
+#endif /* IS_ENABLED(CONFIG_NFS_V4) */
+
+static bool nfs_test_verifier_delegated(unsigned long verf)
+{
+       return verf & 1;
+}
+
+static bool nfs_verifier_is_delegated(struct dentry *dentry)
+{
+       return nfs_test_verifier_delegated(dentry->d_time);
+}
+
+static void nfs_set_verifier_locked(struct dentry *dentry, unsigned long verf)
+{
+       struct inode *inode = d_inode(dentry);
+
+       if (!nfs_verifier_is_delegated(dentry) &&
+           !nfs_verify_change_attribute(d_inode(dentry->d_parent), verf))
+               goto out;
+       if (inode && NFS_PROTO(inode)->have_delegation(inode, FMODE_READ))
+               nfs_set_verifier_delegated(&verf);
+out:
+       dentry->d_time = verf;
+}
+
+/**
+ * nfs_set_verifier - save a parent directory verifier in the dentry
+ * @dentry: pointer to dentry
+ * @verf: verifier to save
+ *
+ * Saves the parent directory verifier in @dentry. If the inode has
+ * a delegation, we also tag the dentry as having been revalidated
+ * while holding a delegation so that we know we don't have to
+ * look it up again after a directory change.
+ */
+void nfs_set_verifier(struct dentry *dentry, unsigned long verf)
+{
+
+       spin_lock(&dentry->d_lock);
+       nfs_set_verifier_locked(dentry, verf);
+       spin_unlock(&dentry->d_lock);
+}
+EXPORT_SYMBOL_GPL(nfs_set_verifier);
+
+#if IS_ENABLED(CONFIG_NFS_V4)
+/**
+ * nfs_clear_verifier_delegated - clear the dir verifier delegation tag
+ * @inode: pointer to inode
+ *
+ * Iterates through the dentries in the inode alias list and clears
+ * the tag used to indicate that the dentry has been revalidated
+ * while holding a delegation.
+ * This function is intended for use when the delegation is being
+ * returned or revoked.
+ */
+void nfs_clear_verifier_delegated(struct inode *inode)
+{
+       struct dentry *alias;
+
+       if (!inode)
+               return;
+       spin_lock(&inode->i_lock);
+       hlist_for_each_entry(alias, &inode->i_dentry, d_u.d_alias) {
+               spin_lock(&alias->d_lock);
+               nfs_unset_verifier_delegated(&alias->d_time);
+               spin_unlock(&alias->d_lock);
+       }
+       spin_unlock(&inode->i_lock);
+}
+EXPORT_SYMBOL_GPL(nfs_clear_verifier_delegated);
+#endif /* IS_ENABLED(CONFIG_NFS_V4) */
+
 /*
  * A check for whether or not the parent directory has changed.
  * In the case it has, we assume that the dentries are untrustworthy
@@ -1154,6 +1261,7 @@ nfs_lookup_revalidate_dentry(struct inode *dir, struct dentry *dentry,
        struct nfs_fh *fhandle;
        struct nfs_fattr *fattr;
        struct nfs4_label *label;
+       unsigned long dir_verifier;
        int ret;
 
        ret = -ENOMEM;
@@ -1163,6 +1271,7 @@ nfs_lookup_revalidate_dentry(struct inode *dir, struct dentry *dentry,
        if (fhandle == NULL || fattr == NULL || IS_ERR(label))
                goto out;
 
+       dir_verifier = nfs_save_change_attribute(dir);
        ret = NFS_PROTO(dir)->lookup(dir, dentry, fhandle, fattr, label);
        if (ret < 0) {
                switch (ret) {
@@ -1183,7 +1292,7 @@ nfs_lookup_revalidate_dentry(struct inode *dir, struct dentry *dentry,
                goto out;
 
        nfs_setsecurity(inode, fattr, label);
-       nfs_set_verifier(dentry, nfs_save_change_attribute(dir));
+       nfs_set_verifier(dentry, dir_verifier);
 
        /* set a readdirplus hint that we had a cache miss */
        nfs_force_use_readdirplus(dir);
@@ -1225,7 +1334,7 @@ nfs_do_lookup_revalidate(struct inode *dir, struct dentry *dentry,
                goto out_bad;
        }
 
-       if (NFS_PROTO(dir)->have_delegation(inode, FMODE_READ))
+       if (nfs_verifier_is_delegated(dentry))
                return nfs_lookup_revalidate_delegated(dir, dentry, inode);
 
        /* Force a full look up iff the parent directory has changed */
@@ -1410,6 +1519,7 @@ struct dentry *nfs_lookup(struct inode *dir, struct dentry * dentry, unsigned in
        struct nfs_fh *fhandle = NULL;
        struct nfs_fattr *fattr = NULL;
        struct nfs4_label *label = NULL;
+       unsigned long dir_verifier;
        int error;
 
        dfprintk(VFS, "NFS: lookup(%pd2)\n", dentry);
@@ -1435,6 +1545,7 @@ struct dentry *nfs_lookup(struct inode *dir, struct dentry * dentry, unsigned in
        if (IS_ERR(label))
                goto out;
 
+       dir_verifier = nfs_save_change_attribute(dir);
        trace_nfs_lookup_enter(dir, dentry, flags);
        error = NFS_PROTO(dir)->lookup(dir, dentry, fhandle, fattr, label);
        if (error == -ENOENT)
@@ -1458,7 +1569,7 @@ struct dentry *nfs_lookup(struct inode *dir, struct dentry * dentry, unsigned in
                        goto out_label;
                dentry = res;
        }
-       nfs_set_verifier(dentry, nfs_save_change_attribute(dir));
+       nfs_set_verifier(dentry, dir_verifier);
 out_label:
        trace_nfs_lookup_exit(dir, dentry, flags, error);
        nfs4_label_free(label);
@@ -1663,7 +1774,7 @@ nfs4_do_lookup_revalidate(struct inode *dir, struct dentry *dentry,
        if (inode == NULL)
                goto full_reval;
 
-       if (NFS_PROTO(dir)->have_delegation(inode, FMODE_READ))
+       if (nfs_verifier_is_delegated(dentry))
                return nfs_lookup_revalidate_delegated(dir, dentry, inode);
 
        /* NFS only supports OPEN on regular files */