[PATCH 2/2] ntdll: Use swapping method to return . and .. as first entries. (try 5)

Grazvydas Ignotas notasas at gmail.com
Thu Aug 4 15:24:49 CDT 2011


The old method didn't handle ReturnSingleEntry and RestartScan flags
correctly. The basic idea of new method is to swap first 2 entries
returned by getdents with . and .. if . and .. are not returned first.

Fixes bug 27877.

Changes:
since try 1: Avoid calling getdents64 twice when possible.
since try 2: Rename and document helper functions,
             handle case of missing inode better, reduse diffstat a bit.
since try 3: Avoid HeapAlloc.
since try 4: Use correct buffer (oops), handle dirent buffer overwrite.
---
 dlls/ntdll/directory.c       |  134 ++++++++++++++++++++++++++++--------------
 dlls/ntdll/tests/directory.c |   13 +---
 2 files changed, 92 insertions(+), 55 deletions(-)

diff --git a/dlls/ntdll/directory.c b/dlls/ntdll/directory.c
index 42b3639..548189c 100644
--- a/dlls/ntdll/directory.c
+++ b/dlls/ntdll/directory.c
@@ -1486,22 +1486,56 @@ static int read_directory_vfat( int fd, IO_STATUS_BLOCK *io, void *buffer, ULONG
 #endif /* VFAT_IOCTL_READDIR_BOTH */
 
 
+#ifdef USE_GETDENTS
+/***********************************************************************
+ *           read_first_dent_name
+ *
+ * reads name of first or second dentry (if they have inodes).
+ */
+static char *read_first_dent_name( int which, int fd, off_t second_offs, KERNEL_DIRENT64 *de_first_two,
+                                   char *buffer, size_t size, BOOL *buffer_changed )
+{
+    KERNEL_DIRENT64 *de;
+    int res;
+
+    de = de_first_two;
+    if (de != NULL)
+    {
+        if (which == 1)
+            de = (KERNEL_DIRENT64 *)((char *)de + de->d_reclen);
+
+        return de->d_ino ? de->d_name : NULL;
+    }
+
+    *buffer_changed = TRUE;
+    lseek( fd, which == 1 ? second_offs : 0, SEEK_SET );
+    res = getdents64( fd, buffer, size );
+    if (res <= 0)
+        return NULL;
+
+    de = (KERNEL_DIRENT64 *)buffer;
+    return de->d_ino ? de->d_name : NULL;
+}
+
 /***********************************************************************
  *           read_directory_getdents
  *
  * Read a directory using the Linux getdents64 system call; helper for NtQueryDirectoryFile.
  */
-#ifdef USE_GETDENTS
 static int read_directory_getdents( int fd, IO_STATUS_BLOCK *io, void *buffer, ULONG length,
                                     BOOLEAN single_entry, const UNICODE_STRING *mask,
                                     BOOLEAN restart_scan, FILE_INFORMATION_CLASS class )
 {
-    off_t old_pos = 0;
+    static off_t second_entry_pos;
+    static struct file_identity last_dir_id;
+    off_t old_pos = 0, next_pos;
     size_t size = length;
-    int res, fake_dot_dot = 1;
     char *data, local_buffer[8192];
-    KERNEL_DIRENT64 *de;
+    KERNEL_DIRENT64 *de, *de_first_two = NULL;
     union file_directory_info *info, *last_info = NULL;
+    const char *filename;
+    BOOL data_buffer_changed;
+    int res, swap_to;
 
     if (size <= sizeof(local_buffer) || !(data = RtlAllocateHeap( GetProcessHeap(), 0, size )))
     {
@@ -1510,7 +1544,7 @@ static int read_directory_getdents( int fd, IO_STATUS_BLOCK *io, void *buffer, U
     }
 
     if (restart_scan) lseek( fd, 0, SEEK_SET );
-    else if (length < max_dir_info_size(class))  /* we may have to return a partial entry here */
+    else
     {
         old_pos = lseek( fd, 0, SEEK_CUR );
         if (old_pos == -1 && errno == ENOENT)
@@ -1522,6 +1556,21 @@ static int read_directory_getdents( int fd, IO_STATUS_BLOCK *io, void *buffer, U
     }
 
     io->u.Status = STATUS_SUCCESS;
+    de = (KERNEL_DIRENT64 *)data;
+
+    /* if old_pos is not 0 we don't know how many entries have been returned already,
+     * so maintain second_entry_pos to know when to return '..' */
+    if (old_pos != 0 && (last_dir_id.dev != curdir.dev || last_dir_id.ino != curdir.ino))
+    {
+        lseek( fd, 0, SEEK_SET );
+        res = getdents64( fd, data, size );
+        if (res > 0)
+        {
+            second_entry_pos = de->d_off;
+            last_dir_id = curdir;
+        }
+        lseek( fd, old_pos, SEEK_SET );
+    }
 
     res = getdents64( fd, data, size );
     if (res == -1)
@@ -1534,52 +1583,46 @@ static int read_directory_getdents( int fd, IO_STATUS_BLOCK *io, void *buffer, U
         goto done;
     }
 
-    de = (KERNEL_DIRENT64 *)data;
+    if (old_pos == 0 && res > 0)
+    {
+        second_entry_pos = de->d_off;
+        last_dir_id = curdir;
+        if (res > de->d_reclen)
+            de_first_two = de;
+    }
 
-    if (restart_scan)
+    while (res > 0)
     {
-        /* check if we got . and .. from getdents */
-        if (res > 0)
-        {
-            if (!strcmp( de->d_name, "." ) && res > de->d_reclen)
-            {
-                KERNEL_DIRENT64 *next_de = (KERNEL_DIRENT64 *)(data + de->d_reclen);
-                if (!strcmp( next_de->d_name, ".." )) fake_dot_dot = 0;
-            }
-        }
-        /* make sure we have enough room for both entries */
-        if (fake_dot_dot)
-        {
-            const ULONG min_info_size = dir_info_size( class, 1 ) + dir_info_size( class, 2 );
-            if (length < min_info_size || single_entry)
-            {
-                FIXME( "not enough room %u/%u for fake . and .. entries\n", length, single_entry );
-                fake_dot_dot = 0;
-            }
-        }
+        res -= de->d_reclen;
+        next_pos = de->d_off;
+        filename = NULL;
 
-        if (fake_dot_dot)
+        /* we must return first 2 entries as "." and "..", but getdents64()
+         * can return them anywhere, so swap first entries with "." and ".." */
+        if (old_pos == 0)
+            filename = ".";
+        else if (old_pos == second_entry_pos)
+            filename = "..";
+        else if (!strcmp( de->d_name, "." ) || !strcmp( de->d_name, ".." ))
         {
-            if ((info = append_entry( buffer, io, length, ".", NULL, mask, class )))
-                last_info = info;
-            if ((info = append_entry( buffer, io, length, "..", NULL, mask, class )))
-                last_info = info;
-
-            /* check if we still have enough space for the largest possible entry */
-            if (last_info && io->Information + max_dir_info_size(class) > length)
+            swap_to = !strcmp( de->d_name, "." ) ? 0 : 1;
+            data_buffer_changed = FALSE;
+
+            filename = read_first_dent_name( swap_to, fd, second_entry_pos, de_first_two,
+                                             data, size, &data_buffer_changed );
+            if (filename != NULL && (!strcmp( filename, "." ) || !strcmp( filename, ".." )))
+                filename = read_first_dent_name( swap_to ^ 1, fd, second_entry_pos, NULL,
+                                                 data, size, &data_buffer_changed );
+            if (data_buffer_changed)
             {
-                lseek( fd, 0, SEEK_SET );  /* reset pos to first entry */
+                lseek( fd, next_pos, SEEK_SET );
                 res = 0;
             }
         }
-    }
+        else if (de->d_ino)
+            filename = de->d_name;
 
-    while (res > 0)
-    {
-        res -= de->d_reclen;
-        if (de->d_ino &&
-            !(fake_dot_dot && (!strcmp( de->d_name, "." ) || !strcmp( de->d_name, ".." ))) &&
-            (info = append_entry( buffer, io, length, de->d_name, NULL, mask, class )))
+        if (filename && (info = append_entry( buffer, io, length, filename, NULL, mask, class )))
         {
             last_info = info;
             if (io->u.Status == STATUS_BUFFER_OVERFLOW)
@@ -1590,17 +1633,18 @@ static int read_directory_getdents( int fd, IO_STATUS_BLOCK *io, void *buffer, U
             /* check if we still have enough space for the largest possible entry */
             if (single_entry || io->Information + max_dir_info_size(class) > length)
             {
-                if (res > 0) lseek( fd, de->d_off, SEEK_SET );  /* set pos to next entry */
+                if (res > 0) lseek( fd, next_pos, SEEK_SET );  /* set pos to next entry */
                 break;
             }
         }
-        old_pos = de->d_off;
+        old_pos = next_pos;
         /* move on to the next entry */
         if (res > 0) de = (KERNEL_DIRENT64 *)((char *)de + de->d_reclen);
         else
         {
             res = getdents64( fd, data, size );
             de = (KERNEL_DIRENT64 *)data;
+            de_first_two = NULL;
         }
     }
 
diff --git a/dlls/ntdll/tests/directory.c b/dlls/ntdll/tests/directory.c
index b32c22a..c74bf34 100644
--- a/dlls/ntdll/tests/directory.c
+++ b/dlls/ntdll/tests/directory.c
@@ -215,16 +215,9 @@ static void test_flags_NtQueryDirectoryFile(OBJECT_ATTRIBUTES *attr, const char
     }
     ok(numfiles < max_test_dir_size, "too many loops\n");
 
-    for (i=0; testfiles[i].name; i++) {
-        if ((strcmp(testfiles[i].name, ".") == 0 || strcmp(testfiles[i].name, "..") == 0) && (single_entry || !restart_flag)) {
-            todo_wine
-            ok(testfiles[i].nfound == 1, "Wrong number %d of %s files found (ReturnSingleEntry=%d,RestartScan=%d)\n",
-              testfiles[i].nfound, testfiles[i].description, single_entry, restart_flag);
-        } else {
-            ok(testfiles[i].nfound == 1, "Wrong number %d of %s files found (ReturnSingleEntry=%d,RestartScan=%d)\n",
-              testfiles[i].nfound, testfiles[i].description, single_entry, restart_flag);
-        }
-    }
+    for (i=0; testfiles[i].name; i++)
+        ok(testfiles[i].nfound == 1, "Wrong number %d of %s files found (ReturnSingleEntry=%d,RestartScan=%d)\n",
+           testfiles[i].nfound, testfiles[i].description, single_entry, restart_flag);
 
     pNtClose(dirh);
 }
-- 
1.7.0.4




More information about the wine-patches mailing list