msxml3: Remove get_ownerDocument() forward.
[wine] / dlls / ntdll / directory.c
index de8d57c..66572da 100644 (file)
 #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
@@ -56,6 +62,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>
@@ -71,6 +80,7 @@
 #include "ntdll_misc.h"
 #include "wine/unicode.h"
 #include "wine/server.h"
+#include "wine/list.h"
 #include "wine/library.h"
 #include "wine/debug.h"
 
@@ -98,8 +108,7 @@ typedef struct
 # define O_DIRECTORY 0200000 /* must be directory */
 #endif
 
-#ifdef __i386__
-
+#ifdef SYS_getdents64
 typedef struct
 {
     ULONG64        d_ino;
@@ -111,21 +120,10 @@ typedef struct
 
 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 */
 
@@ -166,6 +164,7 @@ static const int is_case_sensitive = FALSE;
 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;
@@ -239,6 +238,7 @@ static inline unsigned int dir_info_size( FILE_INFORMATION_CLASS class, unsigned
         return (FIELD_OFFSET( FILE_ID_FULL_DIRECTORY_INFORMATION, FileName[len] ) + 3) & ~3;
     default:
         assert(0);
+        return 0;
     }
 }
 
@@ -248,6 +248,54 @@ static inline unsigned int max_dir_info_size( FILE_INFORMATION_CLASS class )
 }
 
 
+/* 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
  *
@@ -732,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 <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
  *
@@ -1025,6 +1310,7 @@ static union file_directory_info *append_entry( void *info_ptr, IO_STATUS_BLOCK
         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;
@@ -1068,6 +1354,7 @@ static union file_directory_info *append_entry( void *info_ptr, IO_STATUS_BLOCK
 
     default:
         assert(0);
+        return NULL;
     }
     memcpy( filename, long_nameW, total_len - ((char *)filename - (char *)info) );
     io->Information += total_len;
@@ -1327,7 +1614,7 @@ done:
 
 #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
@@ -1512,7 +1799,7 @@ done:
     return res;
 }
 
-#if _DARWIN_FEATURE_64_BIT_INODE
+#ifdef _DARWIN_FEATURE_64_BIT_INODE
 #undef getdirentries
 #undef dirent
 #endif
@@ -1713,6 +2000,10 @@ NTSTATUS WINAPI NtQueryDirectoryFile( HANDLE handle, HANDLE event,
     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;
@@ -1789,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
@@ -2211,6 +2504,139 @@ static inline int get_dos_prefix_len( const UNICODE_STRING *name )
 }
 
 
+/******************************************************************************
+ *           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
  *
@@ -2361,8 +2787,7 @@ NTSTATUS nt_to_unix_file_name_attr( const OBJECT_ATTRIBUTES *attr, ANSI_STRING *
         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)
         {
@@ -2722,49 +3147,72 @@ static void WINAPI read_changes_user_apc( void *arg, IO_STATUS_BLOCK *io, ULONG
 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;
 }