[PATCH 2/2] ntdll: Use getattrlist() if available to check for case-insensitive volumes.

Charles Davis cdavis at mymail.mines.edu
Wed May 19 20:09:22 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.

Also, on Mac OS without getattrlist() and/or the case-sensitivity
attribute, use a file-system heuristic to determine if a volume can
safely be considered case-sensitive. We only do this Mac OS because it
is the only UNIX-like system known to support case-insensitive file
systems.
---
 dlls/ntdll/directory.c |  196 ++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 191 insertions(+), 5 deletions(-)

diff --git a/dlls/ntdll/directory.c b/dlls/ntdll/directory.c
index 865c2fa..fff312e 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,17 @@ 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 */
+
 #define IS_OPTION_TRUE(ch) ((ch) == 'y' || (ch) == 'Y' || (ch) == 't' || (ch) == 'T' || (ch) == '1')
 #define IS_SEPARATOR(ch)   ((ch) == '\\' || (ch) == '/')
 
@@ -783,6 +797,145 @@ static char *get_device_mount_point( dev_t dev )
 }
 
 
+
+static char *get_volume_mount_point( dev_t dev )
+{
+    char *ret = NULL;
+#ifdef __APPLE__
+    struct statfs *entry;
+    struct stat st;
+    int i, size;
+
+    RtlEnterCriticalSection( &dir_section );
+
+    size = getmntinfo( &entry, MNT_NOWAIT );
+    for (i = 0; i < size; i++)
+    {
+        if (stat( entry[i].f_mntfromname, &st ) == -1) continue;
+        if (S_ISBLK(st.st_mode) && st.st_rdev == dev)
+        {
+            ret = RtlAllocateHeap( GetProcessHeap(), 0, strlen(entry[i].f_mntonname) + 1 );
+            if (ret) strcpy( ret, entry[i].f_mntonname );
+            break;
+        }
+    }
+    RtlLeaveCriticalSection( &dir_section );
+#endif
+    return ret;
+}
+
+
+/***********************************************************************
+ *           mount_point_is_case_sensitive
+ *
+ * Checks if the volume at the specified point is case sensitive or not.
+ */
+static inline BOOLEAN mount_point_is_case_sensitive( const char *mntpoint )
+{
+#ifdef __APPLE__
+    struct statfs stfs;
+#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;
+
+    attr.bitmapcount = ATTR_BIT_MAP_COUNT;
+    attr.reserved = 0;
+    attr.commonattr = 0;
+    attr.volattr = ATTR_VOL_CAPABILITIES;
+    attr.dirattr = attr.fileattr = attr.forkattr = 0;
+    if (getattrlist( mntpoint, &attr, &caps, sizeof(caps), 0 ) == 0 &&
+        caps.size == sizeof(caps)) /* Sanity check */
+        return ((caps.caps.capabilities[VOL_CAPABILITIES_FORMAT] & caps.caps.valid[VOL_CAPABILITIES_FORMAT]) & VOL_CAP_FMT_CASE_SENSITIVE) == VOL_CAP_FMT_CASE_SENSITIVE;
+# endif
+    /* Assume the following file systems are always case insensitive on Mac OS:
+     *  FAT
+     *  CDFS (iso9660)
+     *  UDF
+     *  NTFS (not NTFS-3g)
+     * TODO:
+     *  HFS(+) (subtypes 0, 1, 128)
+     *  SMB (subtypes 0, 1, 2, 3, 4)
+     */
+    statfs( mntpoint, &stfs );
+    return strcmp( stfs.f_fstypename, "fat" ) && strcmp( stfs.f_fstypename, "cd9660" ) &&
+        strcmp( stfs.f_fstypename, "udf" ) && strcmp( stfs.f_fstypename, "ntfs" );
+#else
+    return TRUE;
+#endif
+}
+
+/***********************************************************************
+ *           mount_point_is_case_preserving
+ *
+ * Checks if the volume at the specified point preserves the case of
+ * filenames.
+ */
+static inline BOOLEAN mount_point_is_case_preserving( const char *mntpoint )
+{
+#ifdef __APPLE__
+    struct statfs stfs;
+#if defined(HAVE_GETATTRLIST) && defined(ATTR_VOL_CAPABILITIES) && \
+    defined(VOL_CAPABILITIES_FORMAT) && defined(VOL_CAP_FMT_CASE_PRESERVING)
+    struct attrlist attr;
+    wine_vol_caps_t caps;
+
+    attr.bitmapcount = ATTR_BIT_MAP_COUNT;
+    attr.reserved = 0;
+    attr.commonattr = 0;
+    attr.volattr = ATTR_VOL_CAPABILITIES;
+    attr.dirattr = attr.fileattr = attr.forkattr = 0;
+    if (getattrlist( mntpoint, &attr, &caps, sizeof(caps), 0 ) == 0 &&
+        caps.size == sizeof(caps)) /* Sanity check */
+        return ((caps.caps.capabilities[VOL_CAPABILITIES_FORMAT] & caps.caps.valid[VOL_CAPABILITIES_FORMAT]) & VOL_CAP_FMT_CASE_PRESERVING) == VOL_CAP_FMT_CASE_PRESERVING;
+# endif
+    /* Assume the following file systems are always case preserving on Mac OS:
+     *  FAT
+     *  UDF
+     *  NTFS
+     * TODO:
+     *  HFS(+)
+     *  SMB
+     */
+    statfs( mntpoint, &stfs );
+    return strcmp( stfs.f_fstypename, "fat" ) &&
+        strcmp( stfs.f_fstypename, "udf" ) && strcmp( stfs.f_fstypename, "ntfs" );
+#else
+    return TRUE;
+#endif
+}
+
+
+/***********************************************************************
+ *           volume_is_case_sensitive
+ *
+ * Checks if the volume on the specified device is case sensitive or not.
+ */
+static inline BOOLEAN volume_is_case_sensitive( dev_t dev )
+{
+    BOOLEAN ret = TRUE;
+    char *mntpoint = get_volume_mount_point( dev );
+    ret = mount_point_is_case_sensitive( mntpoint );
+    RtlFreeHeap( GetProcessHeap(), 0, mntpoint );
+    return ret;
+}
+
+/***********************************************************************
+ *           volume_is_case_preserving
+ *
+ * Checks if the volume on the specified device preserves the case of
+ * filenames.
+ */
+static inline BOOLEAN volume_is_case_preserving( dev_t dev )
+{
+    BOOLEAN ret = TRUE;
+    char *mntpoint = get_volume_mount_point( dev );
+    ret = mount_point_is_case_preserving( mntpoint );
+    RtlFreeHeap( GetProcessHeap(), 0, mntpoint );
+    return ret;
+}
+
+
 /***********************************************************************
  *           init_options
  *
@@ -1808,7 +1961,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 case_sensitive, int case_preserving,
+                                  int *is_win_dir )
 {
     WCHAR buffer[MAX_DIR_ENTRY_LEN];
     UNICODE_STRING str;
@@ -1834,7 +1988,14 @@ static NTSTATUS find_file_in_dir( char *unix_name, int pos, const WCHAR *name, i
             return STATUS_SUCCESS;
         }
     }
-    if (check_case) goto not_found;  /* we want an exact match */
+    /* three cases:
+     * case sensitive: we want an exact match, so 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 ((check_case && case_sensitive) || !case_preserving) goto not_found;
 
     if (pos > 1) unix_name[pos - 1] = 0;
     else unix_name[1] = 0;  /* keep the initial slash */
@@ -1916,7 +2077,7 @@ 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 );
@@ -1996,12 +2157,19 @@ 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;
+    BOOLEAN case_sensitive, case_preserving;
 
     if (!(unix_name = RtlAllocateHeap( GetProcessHeap(), 0, win_len + MAX_DIR_ENTRY_LEN + 2 )))
         return NULL;
     memcpy( unix_name, windows_dir, win_len );
     pos = win_len;
 
+    /* FIXME: This will break if someone actually mounted something inside
+     * the windows dir. It's not likely, but it can happen.
+     */
+    case_sensitive = mount_point_is_case_sensitive( windows_dir );
+    case_preserving = mount_point_is_case_preserving( windows_dir );
+
     while (*name)
     {
         const WCHAR *end, *next;
@@ -2009,7 +2177,8 @@ static const char *get_redirect_target( const char *windows_dir, const WCHAR *na
         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,
+                                   case_sensitive, case_preserving, NULL );
         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 +2583,9 @@ 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;
+    BOOLEAN case_sensitive = TRUE;
+    BOOLEAN case_preserving = TRUE;
+    dev_t current_vol = 0;
 
     /* try a shortcut first */
 
@@ -2455,6 +2627,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 +2648,16 @@ static NTSTATUS lookup_unix_name( const WCHAR *name, int name_len, char **buffer
             unix_name = *buffer = new_name;
         }
 
+        /* find out if the volume containing the current directory is case-sensitive */
+        if (current_vol != st.st_dev)
+        {
+            current_vol = st.st_dev;
+            case_sensitive = volume_is_case_sensitive( current_vol );
+            case_preserving = volume_is_case_preserving( current_vol );
+        }
         status = find_file_in_dir( unix_name, pos, name, end - name,
-                                   check_case, redirect ? &is_win_dir : NULL );
+                                   check_case, case_sensitive, case_preserving,
+                                   redirect ? &is_win_dir : NULL );
 
         /* if this is the last element, not finding it is not necessarily fatal */
         if (!name_len)
@@ -2502,6 +2686,8 @@ static NTSTATUS lookup_unix_name( const WCHAR *name, int name_len, char **buffer
 
         if (status != STATUS_SUCCESS) break;
 
+        stat( unix_name, &st );
+
         pos += strlen( unix_name + pos );
         name = next;
 
-- 
1.7.1




More information about the wine-patches mailing list