[PATCH 2/2] ntdll: Check for case-insensitive volumes.

Charles Davis cdavis at mymail.mines.edu
Wed Sep 22 13:50:43 CDT 2010


From: Charles Davis <cdavis at mines.edu>

If the volume is indeed case insensitive, we skip searching through the
whole directory ourselves, and just stat() each portion of the path.

With getattrlist(), we check for a particular attribute of the volume to see
if it's case sensitive.

Also, without getattrlist() and/or the case-sensitivity
attribute, use a file-system heuristic to determine if a volume can
safely be considered case-sensitive.
---
 dlls/ntdll/directory.c |  230 ++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 221 insertions(+), 9 deletions(-)

diff --git a/dlls/ntdll/directory.c b/dlls/ntdll/directory.c
index 865c2fa..5b92ce6 100644
--- a/dlls/ntdll/directory.c
+++ b/dlls/ntdll/directory.c
@@ -41,6 +41,9 @@
 #ifdef HAVE_SYS_STAT_H
 # include <sys/stat.h>
 #endif
+#ifdef HAVE_SYS_ATTR_H
+#include <sys/attr.h>
+#endif
 #ifdef HAVE_SYS_IOCTL_H
 #include <sys/ioctl.h>
 #endif
@@ -130,6 +133,24 @@ static inline int getdents64( int fd, char *de, unsigned int size )
 
 #endif  /* linux */
 
+#if defined(HAVE_GETATTRLIST) && defined(ATTR_VOL_CAPABILITIES) && \
+    defined(VOL_CAPABILITIES_FORMAT) && defined(VOL_CAP_FMT_CASE_SENSITIVE)
+
+typedef struct
+{
+    ULONG size;
+    vol_capabilities_attr_t caps;
+} wine_vol_caps_t;
+
+#endif /* HAVE_GETATTRLIST */
+
+typedef enum
+{
+    CASE_SENSITIVE,
+    CASE_INSENSITIVE_PRESERVING,
+    CASE_INSENSITIVE_NONPRESERVING
+} case_sensitivity_t;
+
 #define IS_OPTION_TRUE(ch) ((ch) == 'y' || (ch) == 'Y' || (ch) == 't' || (ch) == 'T' || (ch) == '1')
 #define IS_SEPARATOR(ch)   ((ch) == '\\' || (ch) == '/')
 
@@ -783,6 +804,146 @@ static char *get_device_mount_point( dev_t dev )
 }
 
 
+
+/***********************************************************************
+ *           get_dir_mount_point
+ *
+ * Gets the mount point of the volume containing the specified directory.
+ */
+static inline char *get_dir_mount_point( const char *dir )
+{
+    struct stat st;
+
+    if (stat( dir, &st )) return NULL;
+    return get_device_mount_point( st.st_rdev );
+}
+
+/***********************************************************************
+ *           get_dir_case_sensitivity
+ *
+ * Checks if the volume containing the specified directory is case
+ * sensitive or not.
+ */
+static case_sensitivity_t get_dir_case_sensitivity( const char *dir )
+{
+#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) \
+ || defined(__linux__)
+    struct statfs stfs;
+#elif defined(__NetBSD__)
+    struct statvfs stfs;
+#endif
+
+#if defined(HAVE_GETATTRLIST) && defined(ATTR_VOL_CAPABILITIES) && \
+    defined(VOL_CAPABILITIES_FORMAT) && defined(VOL_CAP_FMT_CASE_SENSITIVE)
+    struct attrlist attr;
+    wine_vol_caps_t caps;
+    char *mntpoint = get_dir_mount_point( dir );
+
+    attr.bitmapcount = ATTR_BIT_MAP_COUNT;
+    attr.reserved = 0;
+    attr.commonattr = 0;
+    attr.volattr = ATTR_VOL_INFO|ATTR_VOL_CAPABILITIES;
+    attr.dirattr = attr.fileattr = attr.forkattr = 0;
+    if (getattrlist( mntpoint, &attr, &caps, sizeof(caps), 0 ) == 0)
+    {
+        RtlFreeHeap( GetProcessHeap(), 0, mntpoint );
+        if (caps.size == sizeof(caps) &&
+            (caps.caps.valid[VOL_CAPABILITIES_FORMAT] &
+             (VOL_CAP_FMT_CASE_SENSITIVE | VOL_CAP_FMT_CASE_PRESERVING)) ==
+            (VOL_CAP_FMT_CASE_SENSITIVE | VOL_CAP_FMT_CASE_PRESERVING))
+        {
+            if ((caps.caps.capabilities[VOL_CAPABILITIES_FORMAT] &
+                VOL_CAP_FMT_CASE_SENSITIVE) != VOL_CAP_FMT_CASE_SENSITIVE)
+            {
+                if ((caps.caps.capabilities[VOL_CAPABILITIES_FORMAT] &
+                    VOL_CAP_FMT_CASE_PRESERVING) == VOL_CAP_FMT_CASE_PRESERVING)
+                    return CASE_INSENSITIVE_PRESERVING;
+                else
+                    return CASE_INSENSITIVE_NONPRESERVING;
+            }
+            else
+                return CASE_SENSITIVE;
+        }
+    }
+    RtlFreeHeap( GetProcessHeap(), 0, mntpoint );
+    /* Fall through */
+#endif
+#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+    statfs( mntpoint, &stfs );
+#ifdef __APPLE__
+    /* Assume CDFS doesn't preserve case, and all others do */
+    if (!strcmp( stfs.f_fstypename, "cd9660" ))
+        return CASE_INSENSITIVE_NONPRESERVING;
+#endif
+    /* Assume these file systems are always case insensitive on Mac OS.
+     * For FreeBSD, only assume CIOPFS is case insensitive (AFAIK, Mac OS
+     * is the only UNIX that supports case-insensitive lookup).
+     */
+    if ((strcmp( stfs.f_fstypename, "fusefs" ) ||
+         strncmp( stfs.f_mntfromname, "ciopfs", 5 ))
+#ifdef __APPLE__
+        &&
+        strcmp( stfs.f_fstypename, "msdos" ) &&
+        strcmp( stfs.f_fstypename, "udf" ) &&
+        strcmp( stfs.f_fstypename, "ntfs" ) &&
+        strcmp( stfs.f_fstypename, "smbfs" ) &&
+#ifdef _DARWIN_FEATURE_64_BIT_INODE
+        (strcmp( stfs.f_fstypename, "hfs" ) || (stfs.f_fssubtype != 0 &&
+                                                stfs.f_fssubtype != 1 &&
+                                                stfs.f_fssubtype != 128))
+#else
+        /* The field says "reserved", but a quick look at the kernel source
+         * tells us that this "reserved" field is really the same as the
+         * "fssubtype" field from the inode64 structure (see munge_statfs()
+         * in <xnu-source>/bsd/vfs/vfs_syscalls.c).
+         */
+        (strcmp( stfs.f_fstypename, "hfs" ) || (stfs.f_reserved1 != 0 &&
+                                                stfs.f_reserved1 != 1 &&
+                                                stfs.f_reserved1 != 128))
+#endif
+#endif
+       )
+        return CASE_SENSITIVE;
+    return CASE_INSENSITIVE_PRESERVING;
+#elif defined(__NetBSD__)
+    statvfs( mntpoint, &stfs );
+    /* Only assume CIOPFS is case insensitive. */
+    if ((strcmp( stfs.f_fstypename, "fusefs" ) ||
+         strncmp( stfs.f_mntfromname, "ciopfs", 5 )))
+        return CASE_INSENSITIVE_PRESERVING;
+    return CASE_SENSITIVE;
+#elif defined(__linux__)
+    statfs( mntpoint, &stfs );
+    /* Only assume CIOPFS is case insensitive. */
+    if (stfs.f_type == 0x65735546 /* FUSE */)
+    {
+        /* Normally, we'd have to parse the mtab to find out exactly what
+         * kind of FUSE FS this is. But, someone on wine-devel suggested
+         * a shortcut. We'll stat a special file in the mount point. If it's
+         * there, we'll assume it's a CIOPFS, else not.
+         * This will break if somebody puts a file named ".ciopfs" in a non-
+         * CIOPFS root.
+         */
+        struct stat st;
+        char *cifile = RtlAllocateHeap( GetProcessHeap(), 0,
+                                        strlen( mntpoint )+sizeof( "/.ciopfs" ) );
+        strcpy( cifile, mntpoint );
+        strcat( cifile, "/.ciopfs" );
+        if (stat( cifile, &st ) == 0)
+        {
+            RtlFreeHeap( GetProcessHeap(), 0, cifile );
+            return CASE_INSENSITIVE_PRESERVING;
+        }
+        RtlFreeHeap( GetProcessHeap(), 0, cifile );
+    }
+    return CASE_SENSITIVE;
+#else
+    FIXME("is not implemented for this platform!\n");
+    return CASE_SENSITIVE;
+#endif
+}
+
+
 /***********************************************************************
  *           init_options
  *
@@ -1808,7 +1969,8 @@ NTSTATUS WINAPI NtQueryDirectoryFile( HANDLE handle, HANDLE event,
  * There must be at least MAX_DIR_ENTRY_LEN+2 chars available at pos.
  */
 static NTSTATUS find_file_in_dir( char *unix_name, int pos, const WCHAR *name, int length,
-                                  int check_case, int *is_win_dir )
+                                  int check_case, int *is_win_dir,
+                                  case_sensitivity_t case_sensitivity )
 {
     WCHAR buffer[MAX_DIR_ENTRY_LEN];
     UNICODE_STRING str;
@@ -1817,6 +1979,7 @@ static NTSTATUS find_file_in_dir( char *unix_name, int pos, const WCHAR *name, i
     struct dirent *de;
     struct stat st;
     int ret, used_default, is_name_8_dot_3;
+    BOOLEAN case_sensitive;
 
     /* try a shortcut for this directory */
 
@@ -1828,13 +1991,30 @@ static NTSTATUS find_file_in_dir( char *unix_name, int pos, const WCHAR *name, i
     if (ret >= 0 && !used_default)
     {
         unix_name[pos + ret] = 0;
-        if (!stat( unix_name, &st ))
+        /* when checking case on a case insensitive but case preserving
+         * fs, don't even bother doing the stat(), so we don't match the 
+         * file case-insensitively.
+         */
+        if (!(case_sensitivity == CASE_INSENSITIVE_PRESERVING && check_case) &&
+            !stat( unix_name, &st ))
         {
             if (is_win_dir) *is_win_dir = is_same_file( &windir, &st );
             return STATUS_SUCCESS;
         }
     }
-    if (check_case) goto not_found;  /* we want an exact match */
+    /* three cases:
+     * case sensitive: when not checking case, do search, else return if
+     *                 stat() fails
+     * not case sensitive, case preserving: when checking case, do search,
+     *                                      else return if stat() fails
+     * not case sensitive, not case preserving: no point in checking case,
+     *                                          so return if stat() fails
+     */
+    if ((case_sensitivity == CASE_SENSITIVE && check_case) ||
+        (case_sensitivity == CASE_INSENSITIVE_PRESERVING && !check_case) ||
+        case_sensitivity == CASE_INSENSITIVE_NONPRESERVING)
+        goto not_found;
+    case_sensitive = (case_sensitivity == CASE_SENSITIVE);
 
     if (pos > 1) unix_name[pos - 1] = 0;
     else unix_name[1] = 0;  /* keep the initial slash */
@@ -1872,7 +2052,9 @@ static NTSTATUS find_file_in_dir( char *unix_name, int pos, const WCHAR *name, i
                     {
                         ret = ntdll_umbstowcs( 0, de[1].d_name, strlen(de[1].d_name),
                                                buffer, MAX_DIR_ENTRY_LEN );
-                        if (ret == length && !memicmpW( buffer, name, length))
+                        if (ret == length && (case_sensitive ?
+                            !memicmpW( buffer, name, length ) :
+                            !memcmp( buffer, name, length*sizeof(WCHAR) )))
                         {
                             strcpy( unix_name + pos, de[1].d_name );
                             RtlLeaveCriticalSection( &dir_section );
@@ -1882,7 +2064,9 @@ static NTSTATUS find_file_in_dir( char *unix_name, int pos, const WCHAR *name, i
                     }
                     ret = ntdll_umbstowcs( 0, de[0].d_name, strlen(de[0].d_name),
                                            buffer, MAX_DIR_ENTRY_LEN );
-                    if (ret == length && !memicmpW( buffer, name, length))
+                    if (ret == length && (case_sensitive ?
+                        !memicmpW( buffer, name, length ) :
+                        !memcmp( buffer, name, length*sizeof(WCHAR) )))
                     {
                         strcpy( unix_name + pos,
                                 de[1].d_name[0] ? de[1].d_name : de[0].d_name );
@@ -1916,7 +2100,9 @@ static NTSTATUS find_file_in_dir( char *unix_name, int pos, const WCHAR *name, i
     while ((de = readdir( dir )))
     {
         ret = ntdll_umbstowcs( 0, de->d_name, strlen(de->d_name), buffer, MAX_DIR_ENTRY_LEN );
-        if (ret == length && !memicmpW( buffer, name, length ))
+        if (ret == length && (case_sensitive ?
+            !memicmpW( buffer, name, length ) :
+            !memcmp( buffer, name, length*sizeof(WCHAR) )))
         {
             strcpy( unix_name + pos, de->d_name );
             closedir( dir );
@@ -1930,7 +2116,9 @@ static NTSTATUS find_file_in_dir( char *unix_name, int pos, const WCHAR *name, i
         {
             WCHAR short_nameW[12];
             ret = hash_short_file_name( &str, short_nameW );
-            if (ret == length && !memicmpW( short_nameW, name, length ))
+            if (ret == length && (case_sensitive ?
+                !memicmpW( short_nameW, name, length ) :
+                !memcmp( short_nameW, name, length*sizeof(WCHAR) )))
             {
                 strcpy( unix_name + pos, de->d_name );
                 closedir( dir );
@@ -1996,6 +2184,9 @@ static const char *get_redirect_target( const char *windows_dir, const WCHAR *na
     int used_default, len, pos, win_len = strlen( windows_dir );
     char *unix_name, *unix_target = NULL;
     NTSTATUS status;
+    case_sensitivity_t sensitivity;
+    dev_t current_vol = 0;
+    struct stat st;
 
     if (!(unix_name = RtlAllocateHeap( GetProcessHeap(), 0, win_len + MAX_DIR_ENTRY_LEN + 2 )))
         return NULL;
@@ -2006,10 +2197,18 @@ static const char *get_redirect_target( const char *windows_dir, const WCHAR *na
     {
         const WCHAR *end, *next;
 
+        stat( unix_name, &st );
+        if (current_vol != st.st_rdev)
+        {
+            current_vol = st.st_rdev;
+            sensitivity = get_dir_case_sensitivity( unix_name );
+        }
+
         for (end = name; *end; end++) if (IS_SEPARATOR(*end)) break;
         for (next = end; *next; next++) if (!IS_SEPARATOR(*next)) break;
 
-        status = find_file_in_dir( unix_name, pos, name, end - name, FALSE, NULL );
+        status = find_file_in_dir( unix_name, pos, name, end - name, FALSE,
+                                   NULL, sensitivity );
         if (status == STATUS_OBJECT_PATH_NOT_FOUND && !*next)  /* not finding last element is ok */
         {
             len = ntdll_wcstoumbs( 0, name, end - name, unix_name + pos + 1,
@@ -2414,6 +2613,8 @@ static NTSTATUS lookup_unix_name( const WCHAR *name, int name_len, char **buffer
     struct stat st;
     char *unix_name = *buffer;
     const BOOL redirect = nb_redirects && ntdll_get_thread_data()->wow64_redir;
+    case_sensitivity_t sensitivity;
+    dev_t current_vol = 0;
 
     /* try a shortcut first */
 
@@ -2455,6 +2656,10 @@ static NTSTATUS lookup_unix_name( const WCHAR *name, int name_len, char **buffer
         const WCHAR *end, *next;
         int is_win_dir = 0;
 
+        unix_name[pos] = 0;
+        stat( unix_name, &st );
+        unix_name[pos] = '/';
+
         end = name;
         while (end < name + name_len && !IS_SEPARATOR(*end)) end++;
         next = end;
@@ -2472,8 +2677,15 @@ static NTSTATUS lookup_unix_name( const WCHAR *name, int name_len, char **buffer
             unix_name = *buffer = new_name;
         }
 
+        /* find out if the current directory is case-sensitive */
+        if (current_vol != st.st_dev)
+        {
+            current_vol = st.st_dev;
+            sensitivity = get_dir_case_sensitivity( unix_name );
+        }
         status = find_file_in_dir( unix_name, pos, name, end - name,
-                                   check_case, redirect ? &is_win_dir : NULL );
+                                   check_case, redirect ? &is_win_dir : NULL,
+                                   sensitivity );
 
         /* if this is the last element, not finding it is not necessarily fatal */
         if (!name_len)
-- 
1.7.2.2




More information about the wine-patches mailing list