}
 }
 
+enum xfs_dacmp
+xfs_da_compname(
+       struct xfs_da_args *args,
+       const char      *name,
+       int             len)
+{
+       return (args->namelen == len && memcmp(args->name, name, len) == 0) ?
+                                       XFS_CMP_EXACT : XFS_CMP_DIFFERENT;
+}
+
+static xfs_dahash_t
+xfs_default_hashname(
+       struct xfs_name *name)
+{
+       return xfs_da_hashname(name->name, name->len);
+}
+
+const struct xfs_nameops xfs_default_nameops = {
+       .hashname       = xfs_default_hashname,
+       .compname       = xfs_da_compname
+};
+
 /*
  * Add a block to the btree ahead of the file.
  * Return the new block number to the caller.
 
  * Btree searching and modification structure definitions.
  *========================================================================*/
 
+/*
+ * Search comparison results
+ */
+enum xfs_dacmp {
+       XFS_CMP_DIFFERENT,      /* names are completely different */
+       XFS_CMP_EXACT,          /* names are exactly the same */
+       XFS_CMP_CASE            /* names are same but differ in case */
+};
+
 /*
  * Structure to ease passing around component names.
  */
        unsigned char   rename;         /* T/F: this is an atomic rename op */
        unsigned char   addname;        /* T/F: this is an add operation */
        unsigned char   oknoent;        /* T/F: ok to return ENOENT, else die */
+       enum xfs_dacmp  cmpresult;      /* name compare result for lookups */
 } xfs_da_args_t;
 
 /*
                (uint)(XFS_DA_LOGOFF(BASE, ADDR)), \
                (uint)(XFS_DA_LOGOFF(BASE, ADDR)+(SIZE)-1)
 
+/*
+ * Name ops for directory and/or attr name operations
+ */
+struct xfs_nameops {
+       xfs_dahash_t    (*hashname)(struct xfs_name *);
+       enum xfs_dacmp  (*compname)(struct xfs_da_args *, const char *, int);
+};
+
 
 #ifdef __KERNEL__
 /*========================================================================
                                          xfs_dabuf_t *dead_buf);
 
 uint xfs_da_hashname(const uchar_t *name_string, int name_length);
+enum xfs_dacmp xfs_da_compname(struct xfs_da_args *args,
+                               const char *name, int len);
+
+
 xfs_da_state_t *xfs_da_state_alloc(void);
 void xfs_da_state_free(xfs_da_state_t *state);
 
 
                (mp->m_dirblksize - (uint)sizeof(xfs_da_node_hdr_t)) /
                (uint)sizeof(xfs_da_node_entry_t);
        mp->m_dir_magicpct = (mp->m_dirblksize * 37) / 100;
+       mp->m_dirnameops = &xfs_default_nameops;
 }
 
 /*
 
        args.name = name->name;
        args.namelen = name->len;
-       args.hashval = xfs_da_hashname(name->name, name->len);
+       args.hashval = dp->i_mount->m_dirnameops->hashname(name);
        args.inumber = inum;
        args.dp = dp;
        args.firstblock = first;
 
        args.name = name->name;
        args.namelen = name->len;
-       args.hashval = xfs_da_hashname(name->name, name->len);
+       args.hashval = dp->i_mount->m_dirnameops->hashname(name);
        args.dp = dp;
        args.whichfork = XFS_DATA_FORK;
        args.trans = tp;
        args.oknoent = 1;
+       args.cmpresult = XFS_CMP_DIFFERENT;
 
        if (dp->i_d.di_format == XFS_DINODE_FMT_LOCAL)
                rval = xfs_dir2_sf_lookup(&args);
 
        args.name = name->name;
        args.namelen = name->len;
-       args.hashval = xfs_da_hashname(name->name, name->len);
+       args.hashval = dp->i_mount->m_dirnameops->hashname(name);
        args.inumber = ino;
        args.dp = dp;
        args.firstblock = first;
 
        args.name = name->name;
        args.namelen = name->len;
-       args.hashval = xfs_da_hashname(name->name, name->len);
+       args.hashval = dp->i_mount->m_dirnameops->hashname(name);
        args.inumber = inum;
        args.dp = dp;
        args.firstblock = first;
 
        args.name = name->name;
        args.namelen = name->len;
-       args.hashval = xfs_da_hashname(name->name, name->len);
+       args.hashval = dp->i_mount->m_dirnameops->hashname(name);
        args.dp = dp;
        args.whichfork = XFS_DATA_FORK;
        args.trans = tp;
 
        int                     mid;            /* binary search current idx */
        xfs_mount_t             *mp;            /* filesystem mount point */
        xfs_trans_t             *tp;            /* transaction pointer */
+       enum xfs_dacmp          cmp;            /* comparison result */
 
        dp = args->dp;
        tp = args->trans;
                dep = (xfs_dir2_data_entry_t *)
                        ((char *)block + xfs_dir2_dataptr_to_off(mp, addr));
                /*
-                * Compare, if it's right give back buffer & entry number.
+                * Compare name and if it's an exact match, return the index
+                * and buffer. If it's the first case-insensitive match, store
+                * the index and buffer and continue looking for an exact match.
                 */
-               if (dep->namelen == args->namelen &&
-                   dep->name[0] == args->name[0] &&
-                   memcmp(dep->name, args->name, args->namelen) == 0) {
+               cmp = mp->m_dirnameops->compname(args, dep->name, dep->namelen);
+               if (cmp != XFS_CMP_DIFFERENT && cmp != args->cmpresult) {
+                       args->cmpresult = cmp;
                        *bpp = bp;
                        *entno = mid;
-                       return 0;
+                       if (cmp == XFS_CMP_EXACT)
+                               return 0;
                }
-       } while (++mid < be32_to_cpu(btp->count) && be32_to_cpu(blp[mid].hashval) == hash);
+       } while (++mid < be32_to_cpu(btp->count) &&
+                       be32_to_cpu(blp[mid].hashval) == hash);
+
+       ASSERT(args->oknoent);
+       /*
+        * Here, we can only be doing a lookup (not a rename or replace).
+        * If a case-insensitive match was found earlier, return success.
+        */
+       if (args->cmpresult == XFS_CMP_CASE)
+               return 0;
        /*
         * No match, release the buffer and return ENOENT.
         */
-       ASSERT(args->oknoent);
        xfs_da_brelse(tp, bp);
        return XFS_ERROR(ENOENT);
 }
        xfs_dir2_sf_t           *sfp;           /* shortform structure */
        __be16                  *tagp;          /* end of data entry */
        xfs_trans_t             *tp;            /* transaction pointer */
+       struct xfs_name         name;
 
        xfs_dir2_trace_args("sf_to_block", args);
        dp = args->dp;
                tagp = xfs_dir2_data_entry_tag_p(dep);
                *tagp = cpu_to_be16((char *)dep - (char *)block);
                xfs_dir2_data_log_entry(tp, bp, dep);
-               blp[2 + i].hashval = cpu_to_be32(xfs_da_hashname(
-                                       (char *)sfep->name, sfep->namelen));
+               name.name = sfep->name;
+               name.len = sfep->namelen;
+               blp[2 + i].hashval = cpu_to_be32(mp->m_dirnameops->
+                                                       hashname(&name));
                blp[2 + i].address = cpu_to_be32(xfs_dir2_byte_to_dataptr(mp,
                                                 (char *)dep - (char *)block));
                offset = (int)((char *)(tagp + 1) - (char *)block);
 
        xfs_mount_t             *mp;            /* filesystem mount point */
        char                    *p;             /* current data position */
        int                     stale;          /* count of stale leaves */
+       struct xfs_name         name;
 
        mp = dp->i_mount;
        d = bp->data;
                        addr = xfs_dir2_db_off_to_dataptr(mp, mp->m_dirdatablk,
                                (xfs_dir2_data_aoff_t)
                                ((char *)dep - (char *)d));
-                       hash = xfs_da_hashname((char *)dep->name, dep->namelen);
+                       name.name = dep->name;
+                       name.len = dep->namelen;
+                       hash = mp->m_dirnameops->hashname(&name);
                        for (i = 0; i < be32_to_cpu(btp->count); i++) {
                                if (be32_to_cpu(lep[i].address) == addr &&
                                    be32_to_cpu(lep[i].hashval) == hash)
 
        xfs_mount_t             *mp;            /* filesystem mount point */
        xfs_dir2_db_t           newdb;          /* new data block number */
        xfs_trans_t             *tp;            /* transaction pointer */
+       xfs_dabuf_t             *cbp;           /* case match data buffer */
+       enum xfs_dacmp          cmp;            /* name compare result */
 
        dp = args->dp;
        tp = args->trans;
         * Loop over all the entries with the right hash value
         * looking to match the name.
         */
+       cbp = NULL;
        for (lep = &leaf->ents[index], dbp = NULL, curdb = -1;
-            index < be16_to_cpu(leaf->hdr.count) && be32_to_cpu(lep->hashval) == args->hashval;
-            lep++, index++) {
+                               index < be16_to_cpu(leaf->hdr.count) &&
+                               be32_to_cpu(lep->hashval) == args->hashval;
+                               lep++, index++) {
                /*
                 * Skip over stale leaf entries.
                 */
                 * need to pitch the old one and read the new one.
                 */
                if (newdb != curdb) {
-                       if (dbp)
+                       if (dbp != cbp)
                                xfs_da_brelse(tp, dbp);
-                       if ((error =
-                           xfs_da_read_buf(tp, dp,
-                                   xfs_dir2_db_to_da(mp, newdb), -1, &dbp,
-                                   XFS_DATA_FORK))) {
+                       error = xfs_da_read_buf(tp, dp,
+                                               xfs_dir2_db_to_da(mp, newdb),
+                                               -1, &dbp, XFS_DATA_FORK);
+                       if (error) {
                                xfs_da_brelse(tp, lbp);
                                return error;
                        }
                /*
                 * Point to the data entry.
                 */
-               dep = (xfs_dir2_data_entry_t *)
-                     ((char *)dbp->data +
-                      xfs_dir2_dataptr_to_off(mp, be32_to_cpu(lep->address)));
+               dep = (xfs_dir2_data_entry_t *)((char *)dbp->data +
+                       xfs_dir2_dataptr_to_off(mp, be32_to_cpu(lep->address)));
                /*
-                * If it matches then return it.
+                * Compare name and if it's an exact match, return the index
+                * and buffer. If it's the first case-insensitive match, store
+                * the index and buffer and continue looking for an exact match.
                 */
-               if (dep->namelen == args->namelen &&
-                   dep->name[0] == args->name[0] &&
-                   memcmp(dep->name, args->name, args->namelen) == 0) {
-                       *dbpp = dbp;
+               cmp = mp->m_dirnameops->compname(args, dep->name, dep->namelen);
+               if (cmp != XFS_CMP_DIFFERENT && cmp != args->cmpresult) {
+                       args->cmpresult = cmp;
                        *indexp = index;
-                       return 0;
+                       /*
+                        * case exact match: release the stored CI buffer if it
+                        * exists and return the current buffer.
+                        */
+                       if (cmp == XFS_CMP_EXACT) {
+                               if (cbp && cbp != dbp)
+                                       xfs_da_brelse(tp, cbp);
+                               *dbpp = dbp;
+                               return 0;
+                       }
+                       cbp = dbp;
                }
        }
+       ASSERT(args->oknoent);
+       /*
+        * Here, we can only be doing a lookup (not a rename or replace).
+        * If a case-insensitive match was found earlier, release the current
+        * buffer and return the stored CI matching buffer.
+        */
+       if (args->cmpresult == XFS_CMP_CASE) {
+               if (cbp != dbp)
+                       xfs_da_brelse(tp, dbp);
+               *dbpp = cbp;
+               return 0;
+       }
        /*
         * No match found, return ENOENT.
         */
-       ASSERT(args->oknoent);
+       ASSERT(cbp == NULL);
        if (dbp)
                xfs_da_brelse(tp, dbp);
        xfs_da_brelse(tp, lbp);
 
        xfs_mount_t             *mp;            /* filesystem mount point */
        xfs_dir2_db_t           newdb;          /* new data block number */
        xfs_trans_t             *tp;            /* transaction pointer */
+       enum xfs_dacmp          cmp;            /* comparison result */
 
        dp = args->dp;
        tp = args->trans;
                dep = (xfs_dir2_data_entry_t *)((char *)curbp->data +
                        xfs_dir2_dataptr_to_off(mp, be32_to_cpu(lep->address)));
                /*
-                * Compare the entry, return it if it matches.
+                * Compare the entry and if it's an exact match, return
+                * EEXIST immediately. If it's the first case-insensitive
+                * match, store the inode number and continue looking.
                 */
-               if (dep->namelen == args->namelen && memcmp(dep->name,
-                                       args->name, args->namelen) == 0) {
+               cmp = mp->m_dirnameops->compname(args, dep->name, dep->namelen);
+               if (cmp != XFS_CMP_DIFFERENT && cmp != args->cmpresult) {
+                       args->cmpresult = cmp;
                        args->inumber = be64_to_cpu(dep->inumber);
                        di = (int)((char *)dep - (char *)curbp->data);
                        error = EEXIST;
-                       goto out;
+                       if (cmp == XFS_CMP_EXACT)
+                               goto out;
                }
        }
-       /* Didn't find a match. */
+       /* Didn't find an exact match. */
        error = ENOENT;
        di = -1;
        ASSERT(index == be16_to_cpu(leaf->hdr.count) || args->oknoent);
        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 */
        /*
         * Release the btree blocks and leaf block.
         */
         * Look up the entry we're deleting, set up the cursor.
         */
        error = xfs_da_node_lookup_int(state, &rval);
-       if (error) {
+       if (error)
                rval = error;
-       }
        /*
         * Didn't find it, upper layer screwed up.
         */
         */
        error = xfs_dir2_leafn_remove(args, blk->bp, blk->index,
                &state->extrablk, &rval);
-       if (error) {
+       if (error)
                return error;
-       }
        /*
         * Fix the hash values up the btree.
         */
 
        int                     i;              /* entry index */
        xfs_dir2_sf_entry_t     *sfep;          /* shortform directory entry */
        xfs_dir2_sf_t           *sfp;           /* shortform structure */
+       enum xfs_dacmp          cmp;            /* comparison result */
 
        xfs_dir2_trace_args("sf_lookup", args);
        xfs_dir2_sf_check(args);
         */
        if (args->namelen == 1 && args->name[0] == '.') {
                args->inumber = dp->i_ino;
+               args->cmpresult = XFS_CMP_EXACT;
                return XFS_ERROR(EEXIST);
        }
        /*
        if (args->namelen == 2 &&
            args->name[0] == '.' && args->name[1] == '.') {
                args->inumber = xfs_dir2_sf_get_inumber(sfp, &sfp->hdr.parent);
+               args->cmpresult = XFS_CMP_EXACT;
                return XFS_ERROR(EEXIST);
        }
        /*
         * Loop over all the entries trying to match ours.
         */
-       for (i = 0, sfep = xfs_dir2_sf_firstentry(sfp);
-            i < sfp->hdr.count;
-            i++, sfep = xfs_dir2_sf_nextentry(sfp, sfep)) {
-               if (sfep->namelen == args->namelen &&
-                   sfep->name[0] == args->name[0] &&
-                   memcmp(args->name, sfep->name, args->namelen) == 0) {
-                       args->inumber =
-                               xfs_dir2_sf_get_inumber(sfp,
-                                       xfs_dir2_sf_inumberp(sfep));
-                       return XFS_ERROR(EEXIST);
+       for (i = 0, sfep = xfs_dir2_sf_firstentry(sfp); i < sfp->hdr.count;
+                               i++, sfep = xfs_dir2_sf_nextentry(sfp, sfep)) {
+               /*
+                * Compare name and if it's an exact match, return the inode
+                * number. If it's the first case-insensitive match, store the
+                * inode number and continue looking for an exact match.
+                */
+               cmp = dp->i_mount->m_dirnameops->compname(args, sfep->name,
+                                                               sfep->namelen);
+               if (cmp != XFS_CMP_DIFFERENT && cmp != args->cmpresult) {
+                       args->cmpresult = cmp;
+                       args->inumber = xfs_dir2_sf_get_inumber(sfp,
+                                               xfs_dir2_sf_inumberp(sfep));
+                       if (cmp == XFS_CMP_EXACT)
+                               return XFS_ERROR(EEXIST);
                }
        }
+       ASSERT(args->oknoent);
+       /*
+        * 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)
+               return XFS_ERROR(EEXIST);
        /*
         * Didn't find it.
         */
-       ASSERT(args->oknoent);
        return XFS_ERROR(ENOENT);
 }
 
         * Loop over the old directory entries.
         * Find the one we're deleting.
         */
-       for (i = 0, sfep = xfs_dir2_sf_firstentry(sfp);
-            i < sfp->hdr.count;
-            i++, sfep = xfs_dir2_sf_nextentry(sfp, sfep)) {
-               if (sfep->namelen == args->namelen &&
-                   sfep->name[0] == args->name[0] &&
-                   memcmp(sfep->name, args->name, args->namelen) == 0) {
+       for (i = 0, sfep = xfs_dir2_sf_firstentry(sfp); i < sfp->hdr.count;
+                               i++, sfep = xfs_dir2_sf_nextentry(sfp, sfep)) {
+               if (xfs_da_compname(args, sfep->name, sfep->namelen) ==
+                                                               XFS_CMP_EXACT) {
                        ASSERT(xfs_dir2_sf_get_inumber(sfp,
-                                       xfs_dir2_sf_inumberp(sfep)) ==
-                               args->inumber);
+                                               xfs_dir2_sf_inumberp(sfep)) ==
+                                                               args->inumber);
                        break;
                }
        }
        /*
         * Didn't find it.
         */
-       if (i == sfp->hdr.count) {
+       if (i == sfp->hdr.count)
                return XFS_ERROR(ENOENT);
-       }
        /*
         * Calculate sizes.
         */
         */
        else {
                for (i = 0, sfep = xfs_dir2_sf_firstentry(sfp);
-                    i < sfp->hdr.count;
-                    i++, sfep = xfs_dir2_sf_nextentry(sfp, sfep)) {
-                       if (sfep->namelen == args->namelen &&
-                           sfep->name[0] == args->name[0] &&
-                           memcmp(args->name, sfep->name, args->namelen) == 0) {
+                               i < sfp->hdr.count;
+                               i++, sfep = xfs_dir2_sf_nextentry(sfp, sfep)) {
+                       if (xfs_da_compname(args, sfep->name, sfep->namelen) ==
+                                                               XFS_CMP_EXACT) {
 #if XFS_BIG_INUMS || defined(DEBUG)
                                ino = xfs_dir2_sf_get_inumber(sfp,
                                        xfs_dir2_sf_inumberp(sfep));
 
 struct xfs_extdelta;
 struct xfs_swapext;
 struct xfs_mru_cache;
+struct xfs_nameops;
 
 /*
  * Prototypes and functions for the Data Migration subsystem.
        __uint8_t               m_inode_quiesce;/* call quiesce on new inodes.
                                                   field governed by m_ilock */
        __uint8_t               m_sectbb_log;   /* sectlog - BBSHIFT */
+       const struct xfs_nameops *m_dirnameops; /* vector of dir name ops */
        int                     m_dirblksize;   /* directory block sz--bytes */
        int                     m_dirblkfsbs;   /* directory block sz--fsbs */
        xfs_dablk_t             m_dirdatablk;   /* blockno of dir data v2 */