#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
+#ifdef HAVE_SYS_SYSCALL_H
+# include <sys/syscall.h>
+#endif
+#ifdef HAVE_SYS_ATTR_H
+#include <sys/attr.h>
+#endif
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#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>
#include "ntdll_misc.h"
#include "wine/unicode.h"
#include "wine/server.h"
+#include "wine/list.h"
#include "wine/library.h"
#include "wine/debug.h"
# define O_DIRECTORY 0200000 /* must be directory */
#endif
-#ifdef __i386__
-
+#ifdef SYS_getdents64
typedef struct
{
ULONG64 d_ino;
static inline int getdents64( int fd, char *de, unsigned int size )
{
- int ret;
- __asm__( "pushl %%ebx; movl %2,%%ebx; int $0x80; popl %%ebx"
- : "=a" (ret)
- : "0" (220 /*NR_getdents64*/), "r" (fd), "c" (de), "d" (size)
- : "memory" );
- if (ret < 0)
- {
- errno = -ret;
- ret = -1;
- }
- return ret;
+ return syscall( SYS_getdents64, fd, de, size );
}
#define USE_GETDENTS
-
-#endif /* i386 */
+#endif
#endif /* linux */
UNICODE_STRING windows_dir = { 0, 0, NULL }; /* windows directory */
UNICODE_STRING system_dir = { 0, 0, NULL }; /* system directory */
+static struct file_identity curdir;
static struct file_identity windir;
static RTL_CRITICAL_SECTION dir_section;
return (FIELD_OFFSET( FILE_ID_FULL_DIRECTORY_INFORMATION, FileName[len] ) + 3) & ~3;
default:
assert(0);
+ return 0;
}
}
}
+/* support for a directory queue for filesystem searches */
+
+struct dir_name
+{
+ struct list entry;
+ char name[1];
+};
+
+static struct list dir_queue = LIST_INIT( dir_queue );
+
+static NTSTATUS add_dir_to_queue( const char *name )
+{
+ int len = strlen( name ) + 1;
+ struct dir_name *dir = RtlAllocateHeap( GetProcessHeap(), 0,
+ FIELD_OFFSET( struct dir_name, name[len] ));
+ if (!dir) return STATUS_NO_MEMORY;
+ strcpy( dir->name, name );
+ list_add_tail( &dir_queue, &dir->entry );
+ return STATUS_SUCCESS;
+}
+
+static NTSTATUS next_dir_in_queue( char *name )
+{
+ struct list *head = list_head( &dir_queue );
+ if (head)
+ {
+ struct dir_name *dir = LIST_ENTRY( head, struct dir_name, entry );
+ strcpy( name, dir->name );
+ list_remove( &dir->entry );
+ RtlFreeHeap( GetProcessHeap(), 0, dir );
+ return STATUS_SUCCESS;
+ }
+ return STATUS_OBJECT_NAME_NOT_FOUND;
+}
+
+static void flush_dir_queue(void)
+{
+ struct list *head;
+
+ while ((head = list_head( &dir_queue )))
+ {
+ struct dir_name *dir = LIST_ENTRY( head, struct dir_name, entry );
+ list_remove( &dir->entry );
+ RtlFreeHeap( GetProcessHeap(), 0, dir );
+ }
+}
+
+
/***********************************************************************
* get_default_com_device
*
}
+#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 <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__)
+ 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
*
io->u.Status = STATUS_BUFFER_OVERFLOW;
}
info = (union file_directory_info *)((char *)info_ptr + io->Information);
+ if (st.st_dev != curdir.dev) st.st_ino = 0; /* ignore inode if on a different device */
/* all the structures start with a FileDirectoryInformation layout */
fill_stat_info( &st, info, class );
info->dir.NextEntryOffset = total_len;
default:
assert(0);
+ return NULL;
}
memcpy( filename, long_nameW, total_len - ((char *)filename - (char *)info) );
io->Information += total_len;
#elif defined HAVE_GETDIRENTRIES
-#if _DARWIN_FEATURE_64_BIT_INODE
+#ifdef _DARWIN_FEATURE_64_BIT_INODE
/* Darwin doesn't provide a version of getdirentries with support for 64-bit
* inodes. When 64-bit inodes are enabled, the getdirentries symbol is mapped
return res;
}
-#if _DARWIN_FEATURE_64_BIT_INODE
+#ifdef _DARWIN_FEATURE_64_BIT_INODE
#undef getdirentries
#undef dirent
#endif
cwd = open( ".", O_RDONLY );
if (fchdir( fd ) != -1)
{
+ struct stat st;
+ fstat( fd, &st );
+ curdir.dev = st.st_dev;
+ curdir.ino = st.st_ino;
#ifdef VFAT_IOCTL_READDIR_BOTH
if ((read_directory_vfat( fd, io, buffer, length, single_entry,
mask, restart_scan, info_class )) != -1) goto done;
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
}
+/******************************************************************************
+ * find_file_id
+ *
+ * Recursively search directories from the dir queue for a given inode.
+ */
+static NTSTATUS find_file_id( ANSI_STRING *unix_name, ULONGLONG file_id, dev_t dev )
+{
+ unsigned int pos;
+ DIR *dir;
+ struct dirent *de;
+ NTSTATUS status;
+ struct stat st;
+
+ while (!(status = next_dir_in_queue( unix_name->Buffer )))
+ {
+ if (!(dir = opendir( unix_name->Buffer ))) continue;
+ TRACE( "searching %s for %s\n", unix_name->Buffer, wine_dbgstr_longlong(file_id) );
+ pos = strlen( unix_name->Buffer );
+ if (pos + MAX_DIR_ENTRY_LEN >= unix_name->MaximumLength/sizeof(WCHAR))
+ {
+ char *new = RtlReAllocateHeap( GetProcessHeap(), 0, unix_name->Buffer,
+ unix_name->MaximumLength * 2 );
+ if (!new)
+ {
+ closedir( dir );
+ return STATUS_NO_MEMORY;
+ }
+ unix_name->MaximumLength *= 2;
+ unix_name->Buffer = new;
+ }
+ unix_name->Buffer[pos++] = '/';
+ while ((de = readdir( dir )))
+ {
+ if (!strcmp( de->d_name, "." ) || !strcmp( de->d_name, ".." )) continue;
+ strcpy( unix_name->Buffer + pos, de->d_name );
+ if (lstat( unix_name->Buffer, &st ) == -1) continue;
+ if (st.st_dev != dev) continue;
+ if (st.st_ino == file_id)
+ {
+ closedir( dir );
+ return STATUS_SUCCESS;
+ }
+ if (!S_ISDIR( st.st_mode )) continue;
+ if ((status = add_dir_to_queue( unix_name->Buffer )) != STATUS_SUCCESS)
+ {
+ closedir( dir );
+ return status;
+ }
+ }
+ closedir( dir );
+ }
+ return status;
+}
+
+
+/******************************************************************************
+ * file_id_to_unix_file_name
+ *
+ * Lookup a file from its file id instead of its name.
+ */
+NTSTATUS file_id_to_unix_file_name( const OBJECT_ATTRIBUTES *attr, ANSI_STRING *unix_name )
+{
+ enum server_fd_type type;
+ int old_cwd, root_fd, needs_close;
+ NTSTATUS status;
+ ULONGLONG file_id;
+ struct stat st, root_st;
+
+ if (attr->ObjectName->Length != sizeof(ULONGLONG)) return STATUS_OBJECT_PATH_SYNTAX_BAD;
+ if (!attr->RootDirectory) return STATUS_INVALID_PARAMETER;
+ memcpy( &file_id, attr->ObjectName->Buffer, sizeof(file_id) );
+
+ unix_name->MaximumLength = 2 * MAX_DIR_ENTRY_LEN + 4;
+ if (!(unix_name->Buffer = RtlAllocateHeap( GetProcessHeap(), 0, unix_name->MaximumLength )))
+ return STATUS_NO_MEMORY;
+ strcpy( unix_name->Buffer, "." );
+
+ if ((status = server_get_unix_fd( attr->RootDirectory, 0, &root_fd, &needs_close, &type, NULL )))
+ goto done;
+
+ if (type != FD_TYPE_DIR)
+ {
+ status = STATUS_OBJECT_TYPE_MISMATCH;
+ goto done;
+ }
+
+ fstat( root_fd, &root_st );
+ if (root_st.st_ino == file_id) /* shortcut for "." */
+ {
+ status = STATUS_SUCCESS;
+ goto done;
+ }
+
+ RtlEnterCriticalSection( &dir_section );
+ if ((old_cwd = open( ".", O_RDONLY )) != -1 && fchdir( root_fd ) != -1)
+ {
+ /* shortcut for ".." */
+ if (!stat( "..", &st ) && st.st_dev == root_st.st_dev && st.st_ino == file_id)
+ {
+ strcpy( unix_name->Buffer, ".." );
+ status = STATUS_SUCCESS;
+ }
+ else
+ {
+ status = add_dir_to_queue( "." );
+ if (!status)
+ status = find_file_id( unix_name, file_id, root_st.st_dev );
+ if (!status) /* get rid of "./" prefix */
+ memmove( unix_name->Buffer, unix_name->Buffer + 2, strlen(unix_name->Buffer) - 1 );
+ flush_dir_queue();
+ }
+ if (fchdir( old_cwd ) == -1) chdir( "/" );
+ }
+ else status = FILE_GetNtStatus();
+ RtlLeaveCriticalSection( &dir_section );
+ if (old_cwd != -1) close( old_cwd );
+
+done:
+ if (status == STATUS_SUCCESS)
+ {
+ TRACE( "%s -> %s\n", wine_dbgstr_longlong(file_id), debugstr_a(unix_name->Buffer) );
+ unix_name->Length = strlen( unix_name->Buffer );
+ }
+ else
+ {
+ TRACE( "%s not found in dir %p\n", wine_dbgstr_longlong(file_id), attr->RootDirectory );
+ RtlFreeHeap( GetProcessHeap(), 0, unix_name->Buffer );
+ }
+ if (needs_close) close( root_fd );
+ return status;
+}
+
+
/******************************************************************************
* lookup_unix_name
*
return STATUS_NO_MEMORY;
unix_name[0] = '.';
- if (!(status = server_get_unix_fd( attr->RootDirectory, FILE_READ_DATA, &root_fd,
- &needs_close, &type, NULL )))
+ if (!(status = server_get_unix_fd( attr->RootDirectory, 0, &root_fd, &needs_close, &type, NULL )))
{
if (type != FD_TYPE_DIR)
{
static NTSTATUS read_changes_apc( void *user, PIO_STATUS_BLOCK iosb, NTSTATUS status, void **apc )
{
struct read_changes_info *info = user;
- char path[PATH_MAX];
- NTSTATUS ret = STATUS_SUCCESS;
- int len, action, i;
+ char data[PATH_MAX];
+ NTSTATUS ret;
+ int size;
SERVER_START_REQ( read_change )
{
req->handle = wine_server_obj_handle( info->FileHandle );
- wine_server_set_reply( req, path, PATH_MAX );
+ wine_server_set_reply( req, data, PATH_MAX );
ret = wine_server_call( req );
- action = reply->action;
- len = wine_server_reply_size( reply );
+ size = wine_server_reply_size( reply );
}
SERVER_END_REQ;
- if (ret == STATUS_SUCCESS && info->Buffer &&
- (info->BufferSize > (sizeof (FILE_NOTIFY_INFORMATION) + len*sizeof(WCHAR))))
+ if (ret == STATUS_SUCCESS && info->Buffer)
{
- PFILE_NOTIFY_INFORMATION pfni;
+ PFILE_NOTIFY_INFORMATION pfni = info->Buffer;
+ int i, left = info->BufferSize;
+ DWORD *last_entry_offset = NULL;
+ struct filesystem_event *event = (struct filesystem_event*)data;
+
+ while (size && left >= sizeof(*pfni))
+ {
+ /* convert to an NT style path */
+ for (i=0; i<event->len; i++)
+ if (event->name[i] == '/')
+ event->name[i] = '\\';
+
+ pfni->Action = event->action;
+ pfni->FileNameLength = ntdll_umbstowcs( 0, event->name, event->len, pfni->FileName,
+ (left - offsetof(FILE_NOTIFY_INFORMATION, FileName)) / sizeof(WCHAR));
+ last_entry_offset = &pfni->NextEntryOffset;
- pfni = info->Buffer;
+ if(pfni->FileNameLength == -1 || pfni->FileNameLength == -2)
+ break;
- /* convert to an NT style path */
- for (i=0; i<len; i++)
- if (path[i] == '/')
- path[i] = '\\';
+ i = offsetof(FILE_NOTIFY_INFORMATION, FileName[pfni->FileNameLength]);
+ pfni->FileNameLength *= sizeof(WCHAR);
+ pfni->NextEntryOffset = i;
+ pfni = (FILE_NOTIFY_INFORMATION*)((char*)pfni + i);
+ left -= i;
- len = ntdll_umbstowcs( 0, path, len, pfni->FileName,
- info->BufferSize - sizeof (*pfni) );
+ i = (offsetof(struct filesystem_event, name[event->len])
+ + sizeof(int)-1) / sizeof(int) * sizeof(int);
+ event = (struct filesystem_event*)((char*)event + i);
+ size -= i;
+ }
- pfni->NextEntryOffset = 0;
- pfni->Action = action;
- pfni->FileNameLength = len * sizeof (WCHAR);
- pfni->FileName[len] = 0;
- len = sizeof (*pfni) - sizeof (DWORD) + pfni->FileNameLength;
+ if (size)
+ {
+ ret = STATUS_NOTIFY_ENUM_DIR;
+ size = 0;
+ }
+ else
+ {
+ *last_entry_offset = 0;
+ size = info->BufferSize - left;
+ }
}
else
{
ret = STATUS_NOTIFY_ENUM_DIR;
- len = 0;
+ size = 0;
}
iosb->u.Status = ret;
- iosb->Information = len;
+ iosb->Information = size;
*apc = read_changes_user_apc;
return ret;
}