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);
 
 
                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);
        return d_splice_alias(cip->i_vnode, dentry);
 }
 
+STATIC struct dentry *
+xfs_vn_ci_lookup(
+       struct inode    *dir,
+       struct dentry   *dentry,
+       struct nameidata *nd)
+{
+       struct xfs_inode *ip;
+       struct xfs_name xname;
+       struct xfs_name ci_name;
+       struct qstr     dname;
+       int             error;
+
+       if (dentry->d_name.len >= MAXNAMELEN)
+               return ERR_PTR(-ENAMETOOLONG);
+
+       xfs_dentry_to_name(&xname, dentry);
+       error = xfs_lookup(XFS_I(dir), &xname, &ip, &ci_name);
+       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_name.name)
+               return d_splice_alias(ip->i_vnode, dentry);
+
+       /* else case-insensitive match... */
+       dname.name = ci_name.name;
+       dname.len = ci_name.len;
+       dentry = d_add_ci(ip->i_vnode, dentry, &dname);
+       kmem_free(ci_name.name);
+       return dentry;
+}
+
 STATIC int
 xfs_vn_link(
        struct dentry   *old_dentry,
        .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,
 
 
 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;
 
 #define XFS_DA_OP_RENAME       0x0002  /* this is an atomic rename op */
 #define XFS_DA_OP_ADDNAME      0x0004  /* this is an add operation */
 #define XFS_DA_OP_OKNOENT      0x0008  /* lookup/add op, ENOENT ok, else die */
+#define XFS_DA_OP_CILOOKUP     0x0010  /* lookup to return CI name if found */
 
 /*
  * Structure to describe buffer(s) for a block.
 
        return rval;
 }
 
+/*
+ * If doing a CI lookup and case-insensitive match, dup actual name into
+ * args.value. Return EEXIST for success (ie. name found) or an error.
+ */
+int
+xfs_dir_cilookup_result(
+       struct xfs_da_args *args,
+       const char      *name,
+       int             len)
+{
+       if (args->cmpresult == XFS_CMP_DIFFERENT)
+               return ENOENT;
+       if (args->cmpresult != XFS_CMP_CASE ||
+                                       !(args->op_flags & XFS_DA_OP_CILOOKUP))
+               return EEXIST;
+
+       args->value = kmem_alloc(len, KM_MAYFAIL);
+       if (!args->value)
+               return ENOMEM;
+
+       memcpy(args->value, name, len);
+       args->valuelen = len;
+       return EEXIST;
+}
+
 /*
  * Lookup a name in a directory, give back the inode number.
+ * If ci_name is not NULL, returns the actual name in ci_name if it differs
+ * to name, or ci_name->name is set to NULL for an exact match.
  */
+
 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 */
+       struct xfs_name *ci_name)       /* out: actual name if CI match */
 {
        xfs_da_args_t   args;
        int             rval;
        args.whichfork = XFS_DATA_FORK;
        args.trans = tp;
        args.op_flags = XFS_DA_OP_OKNOENT;
+       if (ci_name)
+               args.op_flags |= XFS_DA_OP_CILOOKUP;
        args.cmpresult = XFS_CMP_DIFFERENT;
 
        if (dp->i_d.di_format == XFS_DINODE_FMT_LOCAL)
                rval = xfs_dir2_node_lookup(&args);
        if (rval == EEXIST)
                rval = 0;
-       if (rval == 0)
+       if (!rval) {
                *inum = args.inumber;
+               if (ci_name) {
+                       ci_name->name = args.value;
+                       ci_name->len = args.valuelen;
+               }
+       }
        return rval;
 }
 
 
                                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,
+                               struct xfs_name *ci_name);
 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,
 extern int xfs_dir2_shrink_inode(struct xfs_da_args *args, xfs_dir2_db_t db,
                                struct xfs_dabuf *bp);
 
+extern int xfs_dir_cilookup_result(struct xfs_da_args *args, const char *name,
+                               int len);
+
 #endif /* __XFS_DIR2_H__ */
 
        /*
         * 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.
+        * Fill in inode number, CI name if appropriate, release the block.
         */
        args->inumber = be64_to_cpu(dep->inumber);
+       error = xfs_dir_cilookup_result(args, dep->name, dep->namelen);
        xfs_da_brelse(args->trans, bp);
-       return XFS_ERROR(EEXIST);
+       return XFS_ERROR(error);
 }
 
 /*
 
              ((char *)dbp->data +
               xfs_dir2_dataptr_to_off(dp->i_mount, be32_to_cpu(lep->address)));
        /*
-        * Return the found inode number.
+        * Return the found inode number & CI name if appropriate
         */
        args->inumber = be64_to_cpu(dep->inumber);
+       error = xfs_dir_cilookup_result(args, dep->name, dep->namelen);
        xfs_da_brelse(tp, dbp);
        xfs_da_brelse(tp, lbp);
-       return XFS_ERROR(EEXIST);
+       return XFS_ERROR(error);
 }
 
 /*
 
        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 */
        if (state->extravalid) {
                curbp = state->extrablk.bp;
                curdb = state->extrablk.blkno;
+               di = state->extrablk.index;
        }
        /*
         * Loop over leaf entries with the right hash value.
        }
        /* Didn't find an exact match. */
        error = ENOENT;
-       di = -1;
        ASSERT(index == be16_to_cpu(leaf->hdr.count) ||
                                        (args->op_flags & XFS_DA_OP_OKNOENT));
 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);
        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) {
+               /* If a CI match, dup the actual name and return EEXIST */
+               xfs_dir2_data_entry_t   *dep;
+
+               dep = (xfs_dir2_data_entry_t *)((char *)state->extrablk.bp->
+                                               data + state->extrablk.index);
+               rval = xfs_dir_cilookup_result(args, dep->name, dep->namelen);
+       }
        /*
         * Release the btree blocks and leaf block.
         */
 
 {
        xfs_inode_t             *dp;            /* incore directory inode */
        int                     i;              /* entry index */
+       int                     error;
        xfs_dir2_sf_entry_t     *sfep;          /* shortform directory entry */
        xfs_dir2_sf_t           *sfp;           /* shortform structure */
        enum xfs_dacmp          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);
        /*
         * 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)) {
                /*
                                                xfs_dir2_sf_inumberp(sfep));
                        if (cmp == XFS_CMP_EXACT)
                                return XFS_ERROR(EEXIST);
+                       ci_sfep = sfep;
                }
        }
        ASSERT(args->op_flags & XFS_DA_OP_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 a case-insensitive match was not found, return ENOENT.
         */
-       if (args->cmpresult == XFS_CMP_CASE)
-               return XFS_ERROR(EEXIST);
-       /*
-        * Didn't find it.
-        */
-       return XFS_ERROR(ENOENT);
+       if (!ci_sfep)
+               return XFS_ERROR(ENOENT);
+       /* otherwise process the CI match as required by the caller */
+       error = xfs_dir_cilookup_result(args, ci_sfep->name, ci_sfep->namelen);
+       return XFS_ERROR(error);
 }
 
 /*
 
        return VN_INACTIVE_CACHE;
 }
 
-
+/*
+ * Lookups up an inode from "name". If ci_name is not NULL, then a CI match
+ * is allowed, otherwise it has to be an exact match. If a CI match is found,
+ * ci_name->name will point to a the actual name (caller must free) or
+ * will be set to NULL if an exact match is found.
+ */
 int
 xfs_lookup(
        xfs_inode_t             *dp,
        struct xfs_name         *name,
-       xfs_inode_t             **ipp)
+       xfs_inode_t             **ipp,
+       struct xfs_name         *ci_name)
 {
        xfs_ino_t               inum;
        int                     error;
                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_name);
        xfs_iunlock_map_shared(dp, lock_mode);
 
        if (error)
 
        error = xfs_iget(dp->i_mount, NULL, inum, 0, 0, ipp, 0);
        if (error)
-               goto out;
+               goto out_free_name;
 
        xfs_itrace_ref(*ipp);
        return 0;
 
- out:
+out_free_name:
+       if (ci_name)
+               kmem_free(ci_name->name);
+out:
        *ipp = NULL;
        return error;
 }
 
 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, struct xfs_name *ci_name);
 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,