[PATCH 2/2] ntdll: Check for case-insensitive volumes. (try 4)
Charles Davis
cdavis at mymail.mines.edu
Mon Oct 18 22:16:09 CDT 2010
Changes from try 4:
o Fixed adding an entry to the FS cache. Thanks, Joris!
o Don't call stat(2) more than we need to.
o On a case insensitive file system, Unix behavior is the same as Windows.
---
dlls/ntdll/directory.c | 303 +++++++++++++++++++++++++++++++++++++++++++++---
1 files changed, 288 insertions(+), 15 deletions(-)
diff --git a/dlls/ntdll/directory.c b/dlls/ntdll/directory.c
index 865c2fa..0cd1485 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
@@ -56,6 +59,9 @@
#ifdef HAVE_SYS_MOUNT_H
#include <sys/mount.h>
#endif
+#ifdef HAVE_SYS_STATFS_H
+#include <sys/statfs.h>
+#endif
#include <time.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
@@ -130,6 +136,34 @@ static inline int getdents64( int fd, char *de, unsigned int size )
#endif /* linux */
+#if defined(HAVE_GETATTRLIST)
+
+struct get_fsid
+{
+ ULONG size;
+ fsid_t fsid;
+};
+
+struct fs_cache
+{
+ dev_t dev;
+ fsid_t fsid;
+ BOOLEAN case_sensitive;
+} fs_cache[64];
+
+#if defined(ATTR_VOL_CAPABILITIES) && defined(VOL_CAPABILITIES_FORMAT) && \
+ defined(VOL_CAP_FMT_CASE_SENSITIVE)
+
+struct vol_caps
+{
+ ULONG size;
+ vol_capabilities_attr_t caps;
+};
+
+#endif /* ATTR_VOL_CAPABILITIES */
+
+#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 +817,198 @@ static char *get_device_mount_point( dev_t dev )
}
+
+#ifdef HAVE_GETATTRLIST
+/***********************************************************************
+ * look_up_fs_cache
+ *
+ * Checks if the specified file system is in the cache.
+ */
+static struct fs_cache *look_up_fs_cache( dev_t dev )
+{
+ int i;
+ for (i = 0; i < sizeof(fs_cache)/sizeof(fs_cache[0]); i++)
+ if (fs_cache[i].dev == dev)
+ return fs_cache+i;
+ return NULL;
+}
+
+/***********************************************************************
+ * add_fs_cache
+ *
+ * Adds the specified file system to the cache.
+ */
+static void add_fs_cache( dev_t dev, fsid_t fsid, BOOLEAN case_sensitive )
+{
+ int i;
+ struct fs_cache *entry = look_up_fs_cache( dev );
+ static int once = 0;
+ if (entry)
+ {
+ /* Update the cache */
+ entry->fsid = fsid;
+ entry->case_sensitive = case_sensitive;
+ return;
+ }
+
+ /* Add a new entry */
+ for (i = 0; i < sizeof(fs_cache)/sizeof(fs_cache[0]); i++)
+ if (fs_cache[i].dev == 0)
+ {
+ /* This entry is empty, use it */
+ fs_cache[i].dev = dev;
+ fs_cache[i].fsid = fsid;
+ fs_cache[i].case_sensitive = case_sensitive;
+ return;
+ }
+
+ /* Cache is out of space, warn */
+ if (once++)
+ WARN( "FS cache is out of space, expect performance problems\n" );
+}
+#endif
+
+/***********************************************************************
+ * get_dev_case_sensitivity
+ *
+ * Checks if the volume on the specified device is case
+ * sensitive or not.
+ */
+static BOOLEAN get_device_case_sensitivity( dev_t dev, 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)
+ char *mntpoint = NULL;
+ struct attrlist attr;
+ struct vol_caps caps;
+ struct get_fsid get_fsid;
+
+ /* First get the FS ID of the volume */
+ attr.bitmapcount = ATTR_BIT_MAP_COUNT;
+ attr.reserved = 0;
+ attr.commonattr = ATTR_CMN_FSID;
+ attr.volattr = attr.dirattr = attr.fileattr = attr.forkattr = 0;
+ get_fsid.size = 0;
+ if (getattrlist( dir, &attr, &get_fsid, sizeof(get_fsid), 0 ) == 0)
+ {
+ if (get_fsid.size == sizeof(get_fsid))
+ {
+ /* Try to look it up in the cache */
+ struct fs_cache *entry = look_up_fs_cache( dev );
+ if (entry && !memcmp(&entry->fsid, &get_fsid.fsid, sizeof(fsid_t)))
+ /* Cache lookup succeeded */
+ return entry->case_sensitive;
+ /* Cache is stale at this point, we have to update it */
+ }
+ }
+ mntpoint = get_device_mount_point( dev );
+
+ /* Now look up the case-sensitivity */
+ attr.commonattr = 0;
+ attr.volattr = ATTR_VOL_INFO|ATTR_VOL_CAPABILITIES;
+ if (getattrlist( mntpoint, &attr, &caps, sizeof(caps), 0 ) == 0)
+ {
+ 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))
+ {
+ BOOLEAN ret;
+
+ RtlFreeHeap( GetProcessHeap(), 0, mntpoint );
+ if ((caps.caps.capabilities[VOL_CAPABILITIES_FORMAT] &
+ VOL_CAP_FMT_CASE_SENSITIVE) != VOL_CAP_FMT_CASE_SENSITIVE)
+ ret = FALSE;
+ ret = TRUE;
+ /* Update the cache */
+ if (get_fsid.size == sizeof(get_fsid)) /* sanity check */
+ add_fs_cache( dev, get_fsid.fsid, ret );
+ return ret;
+ }
+ }
+ RtlFreeHeap( GetProcessHeap(), 0, mntpoint );
+ /* Fall through */
+#endif
+#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+ statfs( dir, &stfs );
+ /* 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 ))
+ return FALSE;
+#ifdef __APPLE__
+ if (!strcmp( stfs.f_fstypename, "msdos" ) ||
+ !strcmp( stfs.f_fstypename, "cd9660" ) ||
+ !strcmp( stfs.f_fstypename, "udf" ) ||
+ !strcmp( stfs.f_fstypename, "ntfs" ) ||
+ !strcmp( stfs.f_fstypename, "smbfs" ))
+ return FALSE;
+#ifdef _DARWIN_FEATURE_64_BIT_INODE
+ if (!strcmp( stfs.f_fstypename, "hfs" ) && (stfs.f_fssubtype == 0 ||
+ stfs.f_fssubtype == 1 ||
+ stfs.f_fssubtype == 128))
+ return FALSE;
+#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).
+ */
+ if (!strcmp( stfs.f_fstypename, "hfs" ) && (stfs.f_reserved1 == 0 ||
+ stfs.f_reserved1 == 1 ||
+ stfs.f_reserved1 == 128))
+ return FALSE;
+#endif
+#endif
+ return TRUE;
+#elif defined(__NetBSD__)
+ statvfs( dir, &stfs );
+ /* Only assume CIOPFS is case insensitive. */
+ if (strcmp( stfs.f_fstypename, "fusefs" ) ||
+ strncmp( stfs.f_mntfromname, "ciopfs", 5 ))
+ return TRUE;
+ return FALSE;
+#elif defined(__linux__)
+ statfs( dir, &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 directory. 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 directory.
+ */
+ struct stat st;
+ char *cifile = RtlAllocateHeap( GetProcessHeap(), 0,
+ strlen( dir )+sizeof( "/.ciopfs" ) );
+ strcpy( cifile, dir );
+ strcat( cifile, "/.ciopfs" );
+ if (stat( cifile, &st ) == 0)
+ {
+ RtlFreeHeap( GetProcessHeap(), 0, cifile );
+ return FALSE;
+ }
+ RtlFreeHeap( GetProcessHeap(), 0, cifile );
+ }
+ return TRUE;
+#else
+ FIXME("is not implemented for this platform!\n");
+ return TRUE;
+#endif
+}
+
+
/***********************************************************************
* init_options
*
@@ -1808,7 +2034,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,
+ BOOLEAN case_sensitive, dev_t *dev )
{
WCHAR buffer[MAX_DIR_ENTRY_LEN];
UNICODE_STRING str;
@@ -1831,10 +2058,16 @@ static NTSTATUS find_file_in_dir( char *unix_name, int pos, const WCHAR *name, i
if (!stat( unix_name, &st ))
{
if (is_win_dir) *is_win_dir = is_same_file( &windir, &st );
+ *dev = st.st_dev;
return STATUS_SUCCESS;
}
}
- if (check_case) goto not_found; /* we want an exact match */
+ /* case sensitive: when not checking case, do search, else return if
+ * stat() fails
+ * not case sensitive: always return if stat() fails
+ */
+ if (check_case || !case_sensitive)
+ goto not_found;
if (pos > 1) unix_name[pos - 1] = 0;
else unix_name[1] = 0; /* keep the initial slash */
@@ -1872,7 +2105,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 +2117,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 +2153,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 +2169,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 );
@@ -1946,7 +2187,11 @@ not_found:
return STATUS_OBJECT_PATH_NOT_FOUND;
success:
- if (is_win_dir && !stat( unix_name, &st )) *is_win_dir = is_same_file( &windir, &st );
+ if (!stat( unix_name, &st ))
+ {
+ if (is_win_dir) *is_win_dir = is_same_file( &windir, &st );
+ *dev = st.st_dev;
+ }
return STATUS_SUCCESS;
}
@@ -1996,20 +2241,31 @@ 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 sensitive;
+ dev_t current_vol = 0;
+ struct stat st;
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;
+ stat( unix_name, &st );
while (*name)
{
const WCHAR *end, *next;
+ if (current_vol != st.st_dev)
+ {
+ current_vol = st.st_dev;
+ sensitive = get_device_case_sensitivity( current_vol, 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, sensitive, &st.st_dev );
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,
@@ -2081,7 +2337,8 @@ static void init_redirects(void)
*
* Check if path matches a redirect name. If yes, return matched length.
*/
-static int match_redirect( const WCHAR *path, int len, const WCHAR *redir, int check_case )
+static int match_redirect( const WCHAR *path, int len, const WCHAR *redir, int check_case,
+ int case_sensitive )
{
int i = 0;
@@ -2093,7 +2350,7 @@ static int match_redirect( const WCHAR *path, int len, const WCHAR *redir, int c
while (i < len && IS_SEPARATOR(path[i])) i++;
continue; /* move on to next path component */
}
- else if (check_case)
+ else if (check_case && case_sensitive)
{
if (path[i] != *redir) return 0;
}
@@ -2116,14 +2373,15 @@ static int match_redirect( const WCHAR *path, int len, const WCHAR *redir, int c
*
* Retrieve the Unix path corresponding to a redirected path if any.
*/
-static int get_redirect_path( char *unix_name, int pos, const WCHAR *name, int length, int check_case )
+static int get_redirect_path( char *unix_name, int pos, const WCHAR *name, int length, int check_case,
+ int case_sensitive )
{
unsigned int i;
int len;
for (i = 0; i < nb_redirects; i++)
{
- if ((len = match_redirect( name, length, redirects[i].source, check_case )))
+ if ((len = match_redirect( name, length, redirects[i].source, check_case, case_sensitive )))
{
if (!redirects[i].unix_target) break;
unix_name[pos++] = '/';
@@ -2140,7 +2398,8 @@ static int get_redirect_path( char *unix_name, int pos, const WCHAR *name, int l
static const unsigned int nb_redirects = 0;
-static int get_redirect_path( char *unix_name, int pos, const WCHAR *name, int length, int check_case )
+static int get_redirect_path( char *unix_name, int pos, const WCHAR *name, int length, int check_case,
+ int case_sensitive )
{
return 0;
}
@@ -2414,6 +2673,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;
+ BOOLEAN sensitive = FALSE;
+ dev_t current_vol = 0;
/* try a shortcut first */
@@ -2448,6 +2709,10 @@ static NTSTATUS lookup_unix_name( const WCHAR *name, int name_len, char **buffer
if (check_case && !redirect && (disposition == FILE_OPEN || disposition == FILE_OVERWRITE))
return STATUS_OBJECT_NAME_NOT_FOUND;
+ unix_name[pos] = 0;
+ stat( unix_name, &st );
+ unix_name[pos] = '/';
+
/* now do it component by component */
while (name_len)
@@ -2455,6 +2720,13 @@ static NTSTATUS lookup_unix_name( const WCHAR *name, int name_len, char **buffer
const WCHAR *end, *next;
int is_win_dir = 0;
+ /* find out if the current directory is case-sensitive */
+ if (current_vol != st.st_dev)
+ {
+ current_vol = st.st_dev;
+ sensitive = get_device_case_sensitivity( current_vol, unix_name );
+ }
+
end = name;
while (end < name + name_len && !IS_SEPARATOR(*end)) end++;
next = end;
@@ -2473,7 +2745,8 @@ static NTSTATUS lookup_unix_name( const WCHAR *name, int name_len, char **buffer
}
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,
+ sensitive, &st.st_dev );
/* if this is the last element, not finding it is not necessarily fatal */
if (!name_len)
@@ -2505,7 +2778,7 @@ static NTSTATUS lookup_unix_name( const WCHAR *name, int name_len, char **buffer
pos += strlen( unix_name + pos );
name = next;
- if (is_win_dir && (len = get_redirect_path( unix_name, pos, name, name_len, check_case )))
+ if (is_win_dir && (len = get_redirect_path( unix_name, pos, name, name_len, check_case, sensitive )))
{
name += len;
name_len -= len;
--
1.7.3.1
More information about the wine-patches
mailing list