fat: Fix/Cleanup dcache handling for vfat
OGAWA Hirofumi [Thu, 6 Nov 2008 20:53:51 +0000 (12:53 -0800)]
- Add comments for handling dcache of vfat.

- Separate case-sensitive case and case-insensitive to
  vfat_revalidate() and vfat_ci_revalidate().

  vfat_revalidate() doesn't need to drop case-insensitive negative
  dentry on creation path.

- Current code is missing to set ->d_revalidate to the negative dentry
  created by unlink/etc..

  This sets ->d_revalidate always, and returns 1 for positive
  dentry. Now, we don't need to change ->d_op dynamically anymore,
  so this just uses sb->s_root->d_op to set ->d_op.

- d_find_alias() may return DCACHE_DISCONNECTED dentry. It's not
  the interesting dentry there. This checks it.

- Add missing LOOKUP_PARENT check. We don't need to drop the valid
  negative dentry for (LOOKUP_CREATE | LOOKUP_PARENT) lookup.

- For consistent filename on creation path, this drops negative dentry
  if we can't see intent.

Signed-off-by: OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

fs/fat/namei_vfat.c

index 419deab..d585398 100644 (file)
 #include <linux/namei.h>
 #include "fat.h"
 
-static int vfat_revalidate(struct dentry *dentry, struct nameidata *nd)
+/*
+ * If new entry was created in the parent, it could create the 8.3
+ * alias (the shortname of logname).  So, the parent may have the
+ * negative-dentry which matches the created 8.3 alias.
+ *
+ * If it happened, the negative dentry isn't actually negative
+ * anymore.  So, drop it.
+ */
+static int vfat_revalidate_shortname(struct dentry *dentry)
 {
        int ret = 1;
-
-       if (!dentry->d_inode &&
-           nd && !(nd->flags & LOOKUP_CONTINUE) && (nd->flags & LOOKUP_CREATE))
-               /*
-                * negative dentry is dropped, in order to make sure
-                * to use the name which a user desires if this is
-                * create path.
-                */
+       spin_lock(&dentry->d_lock);
+       if (dentry->d_time != dentry->d_parent->d_inode->i_version)
                ret = 0;
-       else {
-               spin_lock(&dentry->d_lock);
-               if (dentry->d_time != dentry->d_parent->d_inode->i_version)
-                       ret = 0;
-               spin_unlock(&dentry->d_lock);
-       }
+       spin_unlock(&dentry->d_lock);
        return ret;
 }
 
+static int vfat_revalidate(struct dentry *dentry, struct nameidata *nd)
+{
+       /* This is not negative dentry. Always valid. */
+       if (dentry->d_inode)
+               return 1;
+       return vfat_revalidate_shortname(dentry);
+}
+
+static int vfat_revalidate_ci(struct dentry *dentry, struct nameidata *nd)
+{
+       /*
+        * This is not negative dentry. Always valid.
+        *
+        * Note, rename() to existing directory entry will have ->d_inode,
+        * and will use existing name which isn't specified name by user.
+        *
+        * We may be able to drop this positive dentry here. But dropping
+        * positive dentry isn't good idea. So it's unsupported like
+        * rename("filename", "FILENAME") for now.
+        */
+       if (dentry->d_inode)
+               return 1;
+
+       /*
+        * This may be nfsd (or something), anyway, we can't see the
+        * intent of this. So, since this can be for creation, drop it.
+        */
+       if (!nd)
+               return 0;
+
+       /*
+        * Drop the negative dentry, in order to make sure to use the
+        * case sensitive name which is specified by user if this is
+        * for creation.
+        */
+       if (!(nd->flags & (LOOKUP_CONTINUE | LOOKUP_PARENT))) {
+               if (nd->flags & LOOKUP_CREATE)
+                       return 0;
+       }
+
+       return vfat_revalidate_shortname(dentry);
+}
+
 /* returns the length of a struct qstr, ignoring trailing dots */
 static unsigned int vfat_striptail_len(struct qstr *qstr)
 {
@@ -126,25 +166,16 @@ static int vfat_cmp(struct dentry *dentry, struct qstr *a, struct qstr *b)
        return 1;
 }
 
-static struct dentry_operations vfat_dentry_ops[4] = {
-       {
-               .d_hash         = vfat_hashi,
-               .d_compare      = vfat_cmpi,
-       },
-       {
-               .d_revalidate   = vfat_revalidate,
-               .d_hash         = vfat_hashi,
-               .d_compare      = vfat_cmpi,
-       },
-       {
-               .d_hash         = vfat_hash,
-               .d_compare      = vfat_cmp,
-       },
-       {
-               .d_revalidate   = vfat_revalidate,
-               .d_hash         = vfat_hash,
-               .d_compare      = vfat_cmp,
-       }
+static struct dentry_operations vfat_ci_dentry_ops = {
+       .d_revalidate   = vfat_revalidate_ci,
+       .d_hash         = vfat_hashi,
+       .d_compare      = vfat_cmpi,
+};
+
+static struct dentry_operations vfat_dentry_ops = {
+       .d_revalidate   = vfat_revalidate,
+       .d_hash         = vfat_hash,
+       .d_compare      = vfat_cmp,
 };
 
 /* Characters that are undesirable in an MS-DOS file name */
@@ -685,29 +716,35 @@ static struct dentry *vfat_lookup(struct inode *dir, struct dentry *dentry,
        struct fat_slot_info sinfo;
        struct inode *inode;
        struct dentry *alias;
-       int err, table;
+       int err;
 
        lock_super(sb);
-       table = (MSDOS_SB(sb)->options.name_check == 's') ? 2 : 0;
-       dentry->d_op = &vfat_dentry_ops[table];
 
        err = vfat_find(dir, &dentry->d_name, &sinfo);
        if (err) {
                if (err == -ENOENT) {
-                       table++;
                        inode = NULL;
                        goto out;
                }
                goto error;
        }
+
        inode = fat_build_inode(sb, sinfo.de, sinfo.i_pos);
        brelse(sinfo.bh);
        if (IS_ERR(inode)) {
                err = PTR_ERR(inode);
                goto error;
        }
+
        alias = d_find_alias(inode);
-       if (alias) {
+       if (alias && !(alias->d_flags & DCACHE_DISCONNECTED)) {
+               /*
+                * This inode has non DCACHE_DISCONNECTED dentry. This
+                * means, the user did ->lookup() by an another name
+                * (longname vs 8.3 alias of it) in past.
+                *
+                * Switch to new one for reason of locality if possible.
+                */
                if (d_invalidate(alias) == 0)
                        dput(alias);
                else {
@@ -715,15 +752,14 @@ static struct dentry *vfat_lookup(struct inode *dir, struct dentry *dentry,
                        unlock_super(sb);
                        return alias;
                }
-
        }
 out:
        unlock_super(sb);
-       dentry->d_op = &vfat_dentry_ops[table];
+       dentry->d_op = sb->s_root->d_op;
        dentry->d_time = dentry->d_parent->d_inode->i_version;
        dentry = d_splice_alias(inode, dentry);
        if (dentry) {
-               dentry->d_op = &vfat_dentry_ops[table];
+               dentry->d_op = sb->s_root->d_op;
                dentry->d_time = dentry->d_parent->d_inode->i_version;
        }
        return dentry;
@@ -1022,9 +1058,9 @@ static int vfat_fill_super(struct super_block *sb, void *data, int silent)
                return res;
 
        if (MSDOS_SB(sb)->options.name_check != 's')
-               sb->s_root->d_op = &vfat_dentry_ops[0];
+               sb->s_root->d_op = &vfat_ci_dentry_ops;
        else
-               sb->s_root->d_op = &vfat_dentry_ops[2];
+               sb->s_root->d_op = &vfat_dentry_ops;
 
        return 0;
 }