xfs
[Top] [All Lists]

[PATCH 2/4] XFS: Return case-insensitive match for dentry cache

To: xfs@xxxxxxxxxxx
Subject: [PATCH 2/4] XFS: Return case-insensitive match for dentry cache
From: Barry Naujok <bnaujok@xxxxxxx>
Date: Mon, 21 Apr 2008 18:31:05 +1000
Cc: linux-fsdevel@xxxxxxxxxxxxxxx
References: <20080421083103.433280025@chook.melbourne.sgi.com>
Sender: xfs-bounce@xxxxxxxxxxx
User-agent: quilt/0.46-1
This implements the code to store the actual filename found
during a lookup in the dentry cache and to avoid multiple entries
in the dcache pointing to the same inode.

To avoid polluting the dcache, we implement a new directory inode
operations for lookup. xfs_vn_ci_lookup() interacts directly with
the dcache and the code was derived from ntfs_lookup() in
fs/ntfs/namei.c.

The "actual name" is only allocated and returned for a case-
insensitive match and not an actual match.

Signed-off-by: Barry Naujok <bnaujok@xxxxxxx>

---
 fs/xfs/linux-2.6/xfs_export.c |    2 
 fs/xfs/linux-2.6/xfs_iops.c   |  156 +++++++++++++++++++++++++++++++++++++++++-
 fs/xfs/linux-2.6/xfs_iops.h   |    1 
 fs/xfs/xfs_dir2.c             |   24 +++++-
 fs/xfs/xfs_dir2.h             |   32 ++++++++
 fs/xfs/xfs_dir2_block.c       |   11 ++
 fs/xfs/xfs_dir2_leaf.c        |    7 +
 fs/xfs/xfs_dir2_node.c        |   21 ++++-
 fs/xfs/xfs_dir2_sf.c          |    7 +
 fs/xfs/xfs_vfsops.c           |    2 
 fs/xfs/xfs_vnodeops.c         |   17 +++-
 fs/xfs/xfs_vnodeops.h         |    2 
 12 files changed, 264 insertions(+), 18 deletions(-)

Index: kern_ci/fs/xfs/linux-2.6/xfs_export.c
===================================================================
--- kern_ci.orig/fs/xfs/linux-2.6/xfs_export.c
+++ kern_ci/fs/xfs/linux-2.6/xfs_export.c
@@ -215,7 +215,7 @@ xfs_fs_get_parent(
        struct xfs_inode        *cip;
        struct dentry           *parent;
 
-       error = xfs_lookup(XFS_I(child->d_inode), &xfs_name_dotdot, &cip);
+       error = xfs_lookup(XFS_I(child->d_inode), &xfs_name_dotdot, &cip, NULL);
        if (unlikely(error))
                return ERR_PTR(-error);
 
Index: kern_ci/fs/xfs/linux-2.6/xfs_iops.c
===================================================================
--- kern_ci.orig/fs/xfs/linux-2.6/xfs_iops.c
+++ kern_ci/fs/xfs/linux-2.6/xfs_iops.c
@@ -2,6 +2,8 @@
  * Copyright (c) 2000-2005 Silicon Graphics, Inc.
  * All Rights Reserved.
  *
+ * Portions Copyright (c) 2001-2006 Anton Altaparmakov
+ *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
  * published by the Free Software Foundation.
@@ -389,7 +391,7 @@ xfs_vn_lookup(
                return ERR_PTR(-ENAMETOOLONG);
 
        xfs_dentry_to_name(&name, dentry);
-       error = xfs_lookup(XFS_I(dir), &name, &cip);
+       error = xfs_lookup(XFS_I(dir), &name, &cip, NULL);
        if (unlikely(error)) {
                if (unlikely(error != ENOENT))
                        return ERR_PTR(-error);
@@ -400,6 +402,139 @@ xfs_vn_lookup(
        return d_splice_alias(cip->i_vnode, dentry);
 }
 
+STATIC struct dentry *
+xfs_ci_dentry_update(
+       struct dentry   *dent,
+       struct inode    *dent_inode,
+       struct xfs_name *name)
+{
+       int             err;
+       struct dentry   *real_dent;
+       struct dentry   *new_dent;
+       struct qstr     nls_name;
+
+       nls_name.name = name->name;
+       nls_name.len = name->len;
+
+       /*
+        * following code from ntfs_lookup() in fs/ntfs/namei.c
+        */
+
+       nls_name.hash = full_name_hash(nls_name.name, nls_name.len);
+
+       /* Does a dentry matching the nls_name exist already? */
+       real_dent = d_lookup(dent->d_parent, &nls_name);
+       /* If not, create it now. */
+       if (!real_dent) {
+               real_dent = d_alloc(dent->d_parent, &nls_name);
+               if (!real_dent) {
+                       err = -ENOMEM;
+                       goto err_out;
+               }
+               new_dent = d_splice_alias(dent_inode, real_dent);
+               if (new_dent)
+                       dput(real_dent);
+               else
+                       new_dent = real_dent;
+               return new_dent;
+       }
+       /* Matching dentry exists, check if it is negative. */
+       if (real_dent->d_inode) {
+               if (unlikely(real_dent->d_inode != dent_inode)) {
+                       /* This can happen because bad inodes are unhashed. */
+                       BUG_ON(!is_bad_inode(dent_inode));
+                       BUG_ON(!is_bad_inode(real_dent->d_inode));
+               }
+               /*
+                * Already have the inode and the dentry attached, decrement
+                * the reference count to balance the xfs_lookup() we did
+                * earlier on.  We found the dentry using d_lookup() so it
+                * cannot be disconnected and thus we do not need to worry
+                * about any NFS/disconnectedness issues here.
+                */
+               iput(dent_inode);
+               return real_dent;
+       }
+       /*
+        * Negative dentry: instantiate it unless the inode is a directory and
+        * has a 'disconnected' dentry (i.e. IS_ROOT and DCACHE_DISCONNECTED),
+        * in which case d_move() that in place of the found dentry.
+        */
+       if (!S_ISDIR(dent_inode->i_mode)) {
+               /* Not a directory; everything is easy. */
+               d_instantiate(real_dent, dent_inode);
+               return real_dent;
+       }
+       spin_lock(&dcache_lock);
+       if (list_empty(&dent_inode->i_dentry)) {
+               /*
+                * Directory without a 'disconnected' dentry; we need to do
+                * d_instantiate() by hand because it takes dcache_lock which
+                * we already hold.
+                */
+               list_add(&real_dent->d_alias, &dent_inode->i_dentry);
+               real_dent->d_inode = dent_inode;
+               spin_unlock(&dcache_lock);
+               security_d_instantiate(real_dent, dent_inode);
+               return real_dent;
+       }
+       /*
+        * Directory with a 'disconnected' dentry; get a reference to the
+        * 'disconnected' dentry.
+        */
+       new_dent = list_entry(dent_inode->i_dentry.next, struct dentry,
+                       d_alias);
+       dget_locked(new_dent);
+       spin_unlock(&dcache_lock);
+       /* Do security vodoo. */
+       security_d_instantiate(real_dent, dent_inode);
+       /* Move new_dent in place of real_dent. */
+       d_move(new_dent, real_dent);
+       /* Balance the xfs_lookup() we did above. */
+       iput(dent_inode);
+       /* Throw away real_dent. */
+       dput(real_dent);
+       /* Use new_dent as the actual dentry. */
+       return new_dent;
+
+err_out:
+       iput(dent_inode);
+       return ERR_PTR(err);
+}
+
+STATIC struct dentry *
+xfs_vn_ci_lookup(
+       struct inode    *dir,
+       struct dentry   *dentry,
+       struct nameidata *nd)
+{
+       struct xfs_inode *ip;
+       struct xfs_name name;
+       int             ci_match = 0;
+       int             error;
+
+       if (dentry->d_name.len >= MAXNAMELEN)
+               return ERR_PTR(-ENAMETOOLONG);
+
+       xfs_dentry_to_name(&name, dentry);
+       error = xfs_lookup(XFS_I(dir), &name, &ip, &ci_match);
+       if (unlikely(error)) {
+               if (unlikely(error != ENOENT))
+                       return ERR_PTR(-error);
+               d_add(dentry, NULL);
+               return NULL;
+       }
+
+       /* if exact match, just splice and exit */
+       if (!ci_match)
+               return d_splice_alias(ip->i_vnode, dentry);
+
+       /* else case-insensitive match... */
+       dentry = xfs_ci_dentry_update(dentry, ip->i_vnode, &name);
+       xfs_name_free(name.name);
+       return dentry;
+}
+
 STATIC int
 xfs_vn_link(
        struct dentry   *old_dentry,
@@ -911,6 +1046,25 @@ const struct inode_operations xfs_dir_in
        .removexattr            = xfs_vn_removexattr,
 };
 
+const struct inode_operations xfs_dir_ci_inode_operations = {
+       .create                 = xfs_vn_create,
+       .lookup                 = xfs_vn_ci_lookup,
+       .link                   = xfs_vn_link,
+       .unlink                 = xfs_vn_unlink,
+       .symlink                = xfs_vn_symlink,
+       .mkdir                  = xfs_vn_mkdir,
+       .rmdir                  = xfs_vn_rmdir,
+       .mknod                  = xfs_vn_mknod,
+       .rename                 = xfs_vn_rename,
+       .permission             = xfs_vn_permission,
+       .getattr                = xfs_vn_getattr,
+       .setattr                = xfs_vn_setattr,
+       .setxattr               = xfs_vn_setxattr,
+       .getxattr               = xfs_vn_getxattr,
+       .listxattr              = xfs_vn_listxattr,
+       .removexattr            = xfs_vn_removexattr,
+};
+
 const struct inode_operations xfs_symlink_inode_operations = {
        .readlink               = generic_readlink,
        .follow_link            = xfs_vn_follow_link,
Index: kern_ci/fs/xfs/linux-2.6/xfs_iops.h
===================================================================
--- kern_ci.orig/fs/xfs/linux-2.6/xfs_iops.h
+++ kern_ci/fs/xfs/linux-2.6/xfs_iops.h
@@ -20,6 +20,7 @@
 
 extern const struct inode_operations xfs_inode_operations;
 extern const struct inode_operations xfs_dir_inode_operations;
+extern const struct inode_operations xfs_dir_ci_inode_operations;
 extern const struct inode_operations xfs_symlink_inode_operations;
 
 extern const struct file_operations xfs_file_operations;
Index: kern_ci/fs/xfs/xfs_dir2.c
===================================================================
--- kern_ci.orig/fs/xfs/xfs_dir2.c
+++ kern_ci/fs/xfs/xfs_dir2.c
@@ -46,6 +46,8 @@
 
 struct xfs_name xfs_name_dotdot = {"..", 2};
 
+kmem_zone_t    *xfs_name_zone;
+
 extern const struct xfs_nameops xfs_default_nameops;
 
 void
@@ -195,13 +197,16 @@ xfs_dir_createname(
 
 /*
  * Lookup a name in a directory, give back the inode number.
+ * If ci_match is not NULL, sets whether a CI match occurred of not, and
+ * if so, return the actual name in name.
  */
 int
 xfs_dir_lookup(
        xfs_trans_t     *tp,
        xfs_inode_t     *dp,
        struct xfs_name *name,
-       xfs_ino_t       *inum)          /* out: inode number */
+       xfs_ino_t       *inum,          /* out: inode number */
+       int             *ci_match)      /* out: CI match occurred */
 {
        xfs_da_args_t   args;
        int             rval;
@@ -234,8 +239,23 @@ xfs_dir_lookup(
                rval = xfs_dir2_node_lookup(&args);
        if (rval == EEXIST)
                rval = 0;
-       if (rval == 0)
+       if (!rval) {
                *inum = args.inumber;
+               if (ci_match) {
+                       *ci_match = args.cmpresult == XFS_CMP_CASE;
+                       if (*ci_match) {
+                               if (!args.value)
+                                       return ENOMEM;
+                               name->name = args.value;
+                               name->len = args.valuelen;
+                       } else {
+                               ASSERT(args.value == NULL);
+                       }
+               } else {
+                       if (args.value)
+                               xfs_name_free(args.value);
+               }
+       }
        return rval;
 }
 
Index: kern_ci/fs/xfs/xfs_dir2.h
===================================================================
--- kern_ci.orig/fs/xfs/xfs_dir2.h
+++ kern_ci/fs/xfs/xfs_dir2.h
@@ -74,7 +74,8 @@ extern int xfs_dir_createname(struct xfs
                                xfs_fsblock_t *first,
                                struct xfs_bmap_free *flist, xfs_extlen_t tot);
 extern int xfs_dir_lookup(struct xfs_trans *tp, struct xfs_inode *dp,
-                               struct xfs_name *name, xfs_ino_t *inum);
+                               struct xfs_name *name, xfs_ino_t *inum,
+                               int *ci_match);
 extern int xfs_dir_removename(struct xfs_trans *tp, struct xfs_inode *dp,
                                struct xfs_name *name, xfs_ino_t ino,
                                xfs_fsblock_t *first,
@@ -99,4 +100,33 @@ extern int xfs_dir2_isleaf(struct xfs_tr
 extern int xfs_dir2_shrink_inode(struct xfs_da_args *args, xfs_dir2_db_t db,
                                struct xfs_dabuf *bp);
 
+/*
+ * Name handling routines for directories and attrs
+ */
+
+extern struct kmem_zone *xfs_name_zone;
+
+static inline char *
+xfs_name_alloc(void)
+{
+       return kmem_zone_zalloc(xfs_name_zone, KM_MAYFAIL);
+}
+
+static inline void
+xfs_name_free(const char *name)
+{
+       kmem_zone_free(xfs_name_zone, (void *)name);
+}
+
+static inline char *
+xfs_name_dup(const char *src_name, int src_len, int *dest_len)
+{
+       char    *dest_name = xfs_name_alloc();
+       if (dest_name) {
+               memcpy(dest_name, src_name, src_len);
+               *dest_len = src_len;
+       }
+       return dest_name;
+}
+
 #endif /* __XFS_DIR2_H__ */
Index: kern_ci/fs/xfs/xfs_dir2_block.c
===================================================================
--- kern_ci.orig/fs/xfs/xfs_dir2_block.c
+++ kern_ci/fs/xfs/xfs_dir2_block.c
@@ -610,12 +610,19 @@ xfs_dir2_block_lookup(
        /*
         * Get the offset from the leaf entry, to point to the data.
         */
-       dep = (xfs_dir2_data_entry_t *)
-             ((char *)block + xfs_dir2_dataptr_to_off(mp, 
be32_to_cpu(blp[ent].address)));
+       dep = (xfs_dir2_data_entry_t *)((char *)block +
+               xfs_dir2_dataptr_to_off(mp, be32_to_cpu(blp[ent].address)));
        /*
         * Fill in inode number, release the block.
         */
        args->inumber = be64_to_cpu(dep->inumber);
+       /*
+        * If a case-insensitive match, allocate a buffer and copy the actual
+        * name into the buffer. Return it via args->value.
+        */
+       if (args->cmpresult == XFS_CMP_CASE)
+               args->value = xfs_name_dup(dep->name, dep->namelen,
+                                                       &args->valuelen);
        xfs_da_brelse(args->trans, bp);
        return XFS_ERROR(EEXIST);
 }
Index: kern_ci/fs/xfs/xfs_dir2_leaf.c
===================================================================
--- kern_ci.orig/fs/xfs/xfs_dir2_leaf.c
+++ kern_ci/fs/xfs/xfs_dir2_leaf.c
@@ -1301,6 +1301,13 @@ xfs_dir2_leaf_lookup(
         * Return the found inode number.
         */
        args->inumber = be64_to_cpu(dep->inumber);
+       /*
+        * If a case-insensitive match, allocate a buffer and copy the actual
+        * name into the buffer. Return it via args->value.
+        */
+       if (args->cmpresult == XFS_CMP_CASE)
+               args->value = xfs_name_dup(dep->name, dep->namelen,
+                                                       &args->valuelen);
        xfs_da_brelse(tp, dbp);
        xfs_da_brelse(tp, lbp);
        return XFS_ERROR(EEXIST);
Index: kern_ci/fs/xfs/xfs_dir2_node.c
===================================================================
--- kern_ci.orig/fs/xfs/xfs_dir2_node.c
+++ kern_ci/fs/xfs/xfs_dir2_node.c
@@ -549,7 +549,7 @@ xfs_dir2_leafn_lookup_for_entry(
        xfs_dir2_data_entry_t   *dep;           /* data block entry */
        xfs_inode_t             *dp;            /* incore directory inode */
        int                     error;          /* error return value */
-       int                     di;             /* data entry index */
+       int                     di = -1;        /* data entry index */
        int                     index;          /* leaf entry index */
        xfs_dir2_leaf_t         *leaf;          /* leaf structure */
        xfs_dir2_leaf_entry_t   *lep;           /* leaf entry */
@@ -577,6 +577,7 @@ xfs_dir2_leafn_lookup_for_entry(
        if (state->extravalid) {
                curbp = state->extrablk.bp;
                curdb = state->extrablk.blkno;
+               di = state->extrablk.index;
        }
        /*
         * Loop over leaf entries with the right hash value.
@@ -638,7 +639,6 @@ xfs_dir2_leafn_lookup_for_entry(
        }
        /* Didn't find an exact match. */
        error = ENOENT;
-       di = -1;
        ASSERT(index == be16_to_cpu(leaf->hdr.count) || args->oknoent);
 out:
        if (curbp) {
@@ -652,7 +652,7 @@ out:
                state->extravalid = 0;
        }
        /*
-        * Return the index, that will be the insertion point.
+        * Return the index, that will be the deletion point for remove/replace.
         */
        *indexp = index;
        return XFS_ERROR(error);
@@ -1819,8 +1819,19 @@ xfs_dir2_node_lookup(
        error = xfs_da_node_lookup_int(state, &rval);
        if (error)
                rval = error;
-       else if (rval == ENOENT && args->cmpresult == XFS_CMP_CASE)
-               rval = EEXIST;  /* a case-insensitive match was found */
+       else if (rval == ENOENT && args->cmpresult == XFS_CMP_CASE) {
+               /*
+                * A case-insensitive match was found: return the actual
+                * name in args->value and return EEXIST
+                */
+               xfs_dir2_data_entry_t   *dep;
+
+               dep = (xfs_dir2_data_entry_t *)((char *)state->extrablk.bp->
+                                               data + state->extrablk.index);
+               args->value = xfs_name_dup(dep->name, dep->namelen,
+                                                       &args->valuelen);
+               rval = EEXIST;
+       }
        /*
         * Release the btree blocks and leaf block.
         */
Index: kern_ci/fs/xfs/xfs_dir2_sf.c
===================================================================
--- kern_ci.orig/fs/xfs/xfs_dir2_sf.c
+++ kern_ci/fs/xfs/xfs_dir2_sf.c
@@ -815,6 +815,7 @@ xfs_dir2_sf_lookup(
        xfs_dir2_sf_entry_t     *sfep;          /* shortform directory entry */
        xfs_dir2_sf_t           *sfp;           /* shortform structure */
        xfs_dacmp_t             cmp;            /* comparison result */
+       xfs_dir2_sf_entry_t     *ci_sfep;       /* case-insens. entry */
 
        xfs_dir2_trace_args("sf_lookup", args);
        xfs_dir2_sf_check(args);
@@ -852,6 +853,7 @@ xfs_dir2_sf_lookup(
        /*
         * Loop over all the entries trying to match ours.
         */
+       ci_sfep = NULL;
        for (i = 0, sfep = xfs_dir2_sf_firstentry(sfp); i < sfp->hdr.count;
                                i++, sfep = xfs_dir2_sf_nextentry(sfp, sfep)) {
                /*
@@ -875,8 +877,11 @@ xfs_dir2_sf_lookup(
         * Here, we can only be doing a lookup (not a rename or replace).
         * If a case-insensitive match was found earlier, return "found".
         */
-       if (args->cmpresult == XFS_CMP_CASE)
+       if (args->cmpresult == XFS_CMP_CASE) {
+               args->value = xfs_name_dup(ci_sfep->name, ci_sfep->namelen,
+                                                       &args->valuelen);
                return XFS_ERROR(EEXIST);
+       }
        /*
         * Didn't find it.
         */
Index: kern_ci/fs/xfs/xfs_vfsops.c
===================================================================
--- kern_ci.orig/fs/xfs/xfs_vfsops.c
+++ kern_ci/fs/xfs/xfs_vfsops.c
@@ -80,6 +80,7 @@ xfs_init(void)
        xfs_dabuf_zone = kmem_zone_init(sizeof(xfs_dabuf_t), "xfs_dabuf");
        xfs_ifork_zone = kmem_zone_init(sizeof(xfs_ifork_t), "xfs_ifork");
        xfs_trans_zone = kmem_zone_init(sizeof(xfs_trans_t), "xfs_trans");
+       xfs_name_zone = kmem_zone_init(MAXNAMELEN, "xfs_name");
        xfs_acl_zone_init(xfs_acl_zone, "xfs_acl");
        xfs_mru_cache_init();
        xfs_filestream_init();
@@ -179,6 +180,7 @@ xfs_cleanup(void)
        kmem_zone_destroy(xfs_btree_cur_zone);
        kmem_zone_destroy(xfs_inode_zone);
        kmem_zone_destroy(xfs_trans_zone);
+       kmem_zone_destroy(xfs_name_zone);
        kmem_zone_destroy(xfs_da_state_zone);
        kmem_zone_destroy(xfs_dabuf_zone);
        kmem_zone_destroy(xfs_buf_item_zone);
Index: kern_ci/fs/xfs/xfs_vnodeops.c
===================================================================
--- kern_ci.orig/fs/xfs/xfs_vnodeops.c
+++ kern_ci/fs/xfs/xfs_vnodeops.c
@@ -1629,12 +1629,18 @@ xfs_inactive(
        return VN_INACTIVE_CACHE;
 }
 
-
+/*
+ * Lookups up an inode from "name". If ci_match is not NULL, then if a
+ * CI match is found, name->name can be replaced and ci_match is set to 1.
+ * The caller of xfs_lookup must call xfs_name_free(name->name) if
+ * ci_match in non-NULL and the value is non-zero.
+ */
 int
 xfs_lookup(
        xfs_inode_t             *dp,
        struct xfs_name         *name,
-       xfs_inode_t             **ipp)
+       xfs_inode_t             **ipp,
+       int                     *ci_match)
 {
        xfs_ino_t               inum;
        int                     error;
@@ -1646,15 +1652,18 @@ xfs_lookup(
                return XFS_ERROR(EIO);
 
        lock_mode = xfs_ilock_map_shared(dp);
-       error = xfs_dir_lookup(NULL, dp, name, &inum);
+       error = xfs_dir_lookup(NULL, dp, name, &inum, ci_match);
        xfs_iunlock_map_shared(dp, lock_mode);
 
        if (error)
                goto out;
 
        error = xfs_iget(dp->i_mount, NULL, inum, 0, 0, ipp, 0);
-       if (error)
+       if (error) {
+               if (ci_match && *ci_match)
+                       xfs_name_free(name->name);
                goto out;
+       }
 
        xfs_itrace_ref(*ipp);
        return 0;
Index: kern_ci/fs/xfs/xfs_vnodeops.h
===================================================================
--- kern_ci.orig/fs/xfs/xfs_vnodeops.h
+++ kern_ci/fs/xfs/xfs_vnodeops.h
@@ -23,7 +23,7 @@ int xfs_fsync(struct xfs_inode *ip, int 
 int xfs_release(struct xfs_inode *ip);
 int xfs_inactive(struct xfs_inode *ip);
 int xfs_lookup(struct xfs_inode *dp, struct xfs_name *name,
-               struct xfs_inode **ipp);
+               struct xfs_inode **ipp, int *ci_match);
 int xfs_create(struct xfs_inode *dp, struct xfs_name *name, mode_t mode,
                xfs_dev_t rdev, struct xfs_inode **ipp, struct cred *credp);
 int xfs_remove(struct xfs_inode *dp, struct xfs_name *name,

-- 


<Prev in Thread] Current Thread [Next in Thread>