diff --git a/configure b/configure index 013dc0b97ba..124b7cdbdcb 100755 --- a/configure +++ b/configure @@ -5849,6 +5849,7 @@ for ac_header in \ strings.h \ stropts.h \ sys/asoundlib.h \ + sys/attr.h \ sys/cdio.h \ sys/elf32.h \ sys/epoll.h \ @@ -12755,6 +12756,7 @@ for ac_func in \ ftruncate \ futimes \ futimesat \ + getattrlist \ getdirentries \ getopt_long \ getpagesize \ diff --git a/configure.ac b/configure.ac index 277bd8eb51f..eb7b4a1b2b9 100644 --- a/configure.ac +++ b/configure.ac @@ -456,6 +456,7 @@ AC_CHECK_HEADERS(\ strings.h \ stropts.h \ sys/asoundlib.h \ + sys/attr.h \ sys/cdio.h \ sys/elf32.h \ sys/epoll.h \ @@ -1909,6 +1910,7 @@ AC_CHECK_FUNCS(\ ftruncate \ futimes \ futimesat \ + getattrlist \ getdirentries \ getopt_long \ getpagesize \ diff --git a/dlls/ntdll/directory.c b/dlls/ntdll/directory.c index cc4e2875d06..5a5d1015350 100644 --- a/dlls/ntdll/directory.c +++ b/dlls/ntdll/directory.c @@ -44,6 +44,9 @@ #ifdef HAVE_SYS_SYSCALL_H # include #endif +#ifdef HAVE_SYS_ATTR_H +#include +#endif #ifdef HAVE_SYS_IOCTL_H #include #endif @@ -59,6 +62,9 @@ #ifdef HAVE_SYS_MOUNT_H #include #endif +#ifdef HAVE_SYS_STATFS_H +#include +#endif #include #ifdef HAVE_UNISTD_H # include @@ -774,6 +780,243 @@ static char *get_device_mount_point( dev_t dev ) } +#if defined(HAVE_GETATTRLIST) && defined(ATTR_VOL_CAPABILITIES) && \ + defined(VOL_CAPABILITIES_FORMAT) && defined(VOL_CAP_FMT_CASE_SENSITIVE) + +struct get_fsid +{ + ULONG size; + dev_t dev; + fsid_t fsid; +}; + +struct fs_cache +{ + dev_t dev; + fsid_t fsid; + BOOLEAN case_sensitive; +} fs_cache[64]; + +struct vol_caps +{ + ULONG size; + vol_capabilities_attr_t caps; +}; + +/*********************************************************************** + * 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" ); +} + +/*********************************************************************** + * get_dir_case_sensitivity_attr + * + * Checks if the volume containing the specified directory is case + * sensitive or not. Uses getattrlist(2). + */ +static int get_dir_case_sensitivity_attr( const char *dir ) +{ + char *mntpoint = NULL; + struct attrlist attr; + struct vol_caps caps; + struct get_fsid get_fsid; + struct fs_cache *entry; + + /* First get the FS ID of the volume */ + attr.bitmapcount = ATTR_BIT_MAP_COUNT; + attr.reserved = 0; + attr.commonattr = ATTR_CMN_DEVID|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 || + get_fsid.size != sizeof(get_fsid)) + return -1; + /* Try to look it up in the cache */ + entry = look_up_fs_cache( get_fsid.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( get_fsid.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) + { + RtlFreeHeap( GetProcessHeap(), 0, mntpoint ); + add_fs_cache( get_fsid.dev, get_fsid.fsid, TRUE ); + return TRUE; + } + 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)) + { + BOOLEAN ret; + + if ((caps.caps.capabilities[VOL_CAPABILITIES_FORMAT] & + VOL_CAP_FMT_CASE_SENSITIVE) != VOL_CAP_FMT_CASE_SENSITIVE) + ret = FALSE; + else + ret = TRUE; + /* Update the cache */ + add_fs_cache( get_fsid.dev, get_fsid.fsid, ret ); + return ret; + } + return FALSE; +} +#endif + +/*********************************************************************** + * get_dir_case_sensitivity_stat + * + * Checks if the volume containing the specified directory is case + * sensitive or not. Uses statfs(2) or statvfs(2). + */ +static BOOLEAN get_dir_case_sensitivity_stat( const char *dir ) +{ +#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + struct statfs stfs; + + 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 /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__) + struct statvfs stfs; + + 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__) + struct statfs stfs; + struct stat st; + char *cifile; + + /* Only assume CIOPFS is case insensitive. */ + statfs( dir, &stfs ); + if (stfs.f_type != 0x65735546 /* FUSE_SUPER_MAGIC */) + return TRUE; + /* 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. + */ + cifile = RtlAllocateHeap( GetProcessHeap(), 0, strlen( dir )+sizeof("/.ciopfs") ); + if (!cifile) return TRUE; + strcpy( cifile, dir ); + strcat( cifile, "/.ciopfs" ); + if (stat( cifile, &st ) == 0) + { + RtlFreeHeap( GetProcessHeap(), 0, cifile ); + return FALSE; + } + RtlFreeHeap( GetProcessHeap(), 0, cifile ); + return TRUE; +#else + return TRUE; +#endif +} + + +/*********************************************************************** + * get_dir_case_sensitivity + * + * Checks if the volume containing the specified directory is case + * sensitive or not. Uses statfs(2) or statvfs(2). + */ +static BOOLEAN get_dir_case_sensitivity( const char *dir ) +{ +#if defined(HAVE_GETATTRLIST) && defined(ATTR_VOL_CAPABILITIES) && \ + defined(VOL_CAPABILITIES_FORMAT) && defined(VOL_CAP_FMT_CASE_SENSITIVE) + int case_sensitive = get_dir_case_sensitivity_attr( dir ); + if (case_sensitive != -1) return case_sensitive; +#endif + return get_dir_case_sensitivity_stat( dir ); +} + + /*********************************************************************** * init_options * @@ -1837,6 +2080,8 @@ static NTSTATUS find_file_in_dir( char *unix_name, int pos, const WCHAR *name, i str.MaximumLength = str.Length; is_name_8_dot_3 = RtlIsNameLegalDOS8Dot3( &str, NULL, &spaces ) && !spaces; + if (!is_name_8_dot_3 && !get_dir_case_sensitivity( unix_name )) goto not_found; + /* now look for it through the directory */ #ifdef VFAT_IOCTL_READDIR_BOTH diff --git a/include/config.h.in b/include/config.h.in index 2e4370d2867..dab00b2901d 100644 --- a/include/config.h.in +++ b/include/config.h.in @@ -200,6 +200,9 @@ /* Define to 1 if you have the `getaddrinfo' function. */ #undef HAVE_GETADDRINFO +/* Define to 1 if you have the `getattrlist' function. */ +#undef HAVE_GETATTRLIST + /* Define to 1 if you have the `getdirentries' function. */ #undef HAVE_GETDIRENTRIES @@ -875,6 +878,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_SYS_ASOUNDLIB_H +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_ATTR_H + /* Define to 1 if you have the header file. */ #undef HAVE_SYS_CDIO_H