cifs: convert case-insensitive dentry ops to use new case conversion routines
authorJeff Layton <jlayton@redhat.com>
Thu, 5 Sep 2013 12:38:11 +0000 (08:38 -0400)
committerSteve French <smfrench@gmail.com>
Sun, 8 Sep 2013 19:38:08 +0000 (14:38 -0500)
Have the case-insensitive d_compare and d_hash routines convert each
character in the filenames to wchar_t's and then use the new
cifs_toupper routine to convert those into uppercase.

With this scheme we should more closely emulate the case conversion that
the servers will do.

Reported-and-Tested-by: Jan-Marek Glogowski <glogow@fbihome.de>
Signed-off-by: Jeff Layton <jlayton@redhat.com>
Signed-off-by: Steve French <smfrench@gmail.com>
fs/cifs/dir.c

index d62ce0d..d3e2eaa 100644 (file)
@@ -32,6 +32,7 @@
 #include "cifsproto.h"
 #include "cifs_debug.h"
 #include "cifs_fs_sb.h"
+#include "cifs_unicode.h"
 
 static void
 renew_parental_timestamps(struct dentry *direntry)
@@ -834,12 +835,17 @@ static int cifs_ci_hash(const struct dentry *dentry, struct qstr *q)
 {
        struct nls_table *codepage = CIFS_SB(dentry->d_sb)->local_nls;
        unsigned long hash;
-       int i;
+       wchar_t c;
+       int i, charlen;
 
        hash = init_name_hash();
-       for (i = 0; i < q->len; i++)
-               hash = partial_name_hash(nls_tolower(codepage, q->name[i]),
-                                        hash);
+       for (i = 0; i < q->len; i += charlen) {
+               charlen = codepage->char2uni(&q->name[i], q->len - i, &c);
+               /* error out if we can't convert the character */
+               if (unlikely(charlen < 0))
+                       return charlen;
+               hash = partial_name_hash(cifs_toupper(c), hash);
+       }
        q->hash = end_name_hash(hash);
 
        return 0;
@@ -849,11 +855,47 @@ static int cifs_ci_compare(const struct dentry *parent, const struct dentry *den
                unsigned int len, const char *str, const struct qstr *name)
 {
        struct nls_table *codepage = CIFS_SB(parent->d_sb)->local_nls;
+       wchar_t c1, c2;
+       int i, l1, l2;
 
-       if ((name->len == len) &&
-           (nls_strnicmp(codepage, name->name, str, len) == 0))
-               return 0;
-       return 1;
+       /*
+        * We make the assumption here that uppercase characters in the local
+        * codepage are always the same length as their lowercase counterparts.
+        *
+        * If that's ever not the case, then this will fail to match it.
+        */
+       if (name->len != len)
+               return 1;
+
+       for (i = 0; i < len; i += l1) {
+               /* Convert characters in both strings to UTF-16. */
+               l1 = codepage->char2uni(&str[i], len - i, &c1);
+               l2 = codepage->char2uni(&name->name[i], name->len - i, &c2);
+
+               /*
+                * If we can't convert either character, just declare it to
+                * be 1 byte long and compare the original byte.
+                */
+               if (unlikely(l1 < 0 && l2 < 0)) {
+                       if (str[i] != name->name[i])
+                               return 1;
+                       l1 = 1;
+                       continue;
+               }
+
+               /*
+                * Here, we again ass|u|me that upper/lowercase versions of
+                * a character are the same length in the local NLS.
+                */
+               if (l1 != l2)
+                       return 1;
+
+               /* Now compare uppercase versions of these characters */
+               if (cifs_toupper(c1) != cifs_toupper(c2))
+                       return 1;
+       }
+
+       return 0;
 }
 
 const struct dentry_operations cifs_ci_dentry_ops = {