[PATCH 2/4] ntdll: Fix read_directory_getdirentries().

Matteo Bruni mbruni at codeweavers.com
Thu Aug 27 14:08:39 CDT 2015


---
The man page for getdirentries mentions that only positions previously
returned from lseek or getdirentries should be used, I'm arguably abusing
the pointer to store the index and then restoring the position using
SEEK_CUR. It seems to work fine for me, not sure if that might cause
issues with some specific filesystem setups.
---
 dlls/ntdll/directory.c | 143 ++++++++++++++++++-------------------------------
 1 file changed, 53 insertions(+), 90 deletions(-)

diff --git a/dlls/ntdll/directory.c b/dlls/ntdll/directory.c
index f9562d2..eb6cd45 100644
--- a/dlls/ntdll/directory.c
+++ b/dlls/ntdll/directory.c
@@ -1820,27 +1820,27 @@ static int read_directory_getdirentries( int fd, IO_STATUS_BLOCK *io, void *buff
                                          BOOLEAN restart_scan, FILE_INFORMATION_CLASS class )
 {
     long restart_pos;
-    ULONG_PTR restart_info_pos = 0;
-    size_t size, initial_size = length;
-    int res, fake_dot_dot = 1;
-    char *data, local_buffer[8192];
+    off_t newcursor;
+    int total, res, i;
+    char data[8192];
     struct dirent *de;
-    union file_directory_info *info, *last_info = NULL, *restart_last_info = NULL;
+    union file_directory_info *info, *last_info = NULL;
 
-    size = initial_size;
-    data = local_buffer;
-    if (size > sizeof(local_buffer) && !(data = RtlAllocateHeap( GetProcessHeap(), 0, size )))
+    io->u.Status = STATUS_SUCCESS;
+
+    if (restart_scan)
     {
-        io->u.Status = STATUS_NO_MEMORY;
-        return io->u.Status;
+        lseek( fd, 0, SEEK_SET );
+        i = 0;
+    }
+    else
+    {
+        i = lseek( fd, 0, SEEK_CUR );
+        lseek( fd, 0, SEEK_SET );
+        newcursor = lseek( fd, i < 2 ? 0 : i - 2, SEEK_CUR );
     }
 
-    if (restart_scan) lseek( fd, 0, SEEK_SET );
-
-    io->u.Status = STATUS_SUCCESS;
-
-    /* FIXME: should make sure size is larger than filesystem block size */
-    res = wine_getdirentries( fd, data, size, &restart_pos );
+    total = res = wine_getdirentries( fd, data, sizeof(data), &restart_pos );
     if (res == -1)
     {
         io->u.Status = FILE_GetNtStatus();
@@ -1850,102 +1850,65 @@ static int read_directory_getdirentries( int fd, IO_STATUS_BLOCK *io, void *buff
 
     de = (struct dirent *)data;
 
-    if (restart_scan)
+    for (;;)
     {
-        /* check if we got . and .. from getdirentries */
-        if (res > 0)
+        if (!i)
         {
-            if (!strcmp( de->d_name, "." ) && res > dir_reclen(de))
-            {
-                struct dirent *next_de = (struct dirent *)(data + dir_reclen(de));
-                if (!strcmp( next_de->d_name, ".." )) fake_dot_dot = 0;
-            }
+            info = append_entry( buffer, io, length, ".", NULL, mask, class );
         }
-        /* make sure we have enough room for both entries */
-        if (fake_dot_dot)
+        else if (i == 1)
         {
-            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;
-            }
+            info = append_entry( buffer, io, length, "..", NULL, mask, class );
         }
-
-        if (fake_dot_dot)
+        else if (res > 0)
+        {
+            res -= dir_reclen(de);
+            if (de->d_fileno && strcmp( de->d_name, "." ) && strcmp( de->d_name, ".." ))
+                info = append_entry( buffer, io, length, de->d_name, NULL, mask, class );
+            else
+                info = NULL;
+            de = (struct dirent *)((char *)de + dir_reclen(de));
+        }
+        else
         {
-            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;
+            info = NULL;
+        }
+        i++;
 
-            restart_last_info = last_info;
-            restart_info_pos = io->Information;
+        if (info)
+        {
+            last_info = info;
+            if (single_entry)
+                break;
+        }
+        else if (io->u.Status == STATUS_BUFFER_OVERFLOW)
+        {
+            i--;
+            if (last_info)
+                io->u.Status = STATUS_SUCCESS;
+            break;
         }
-    }
 
-    while (res > 0)
-    {
-        res -= dir_reclen(de);
-        if (de->d_fileno &&
-            !(fake_dot_dot && (!strcmp( de->d_name, "." ) || !strcmp( de->d_name, ".." ))))
+        if (!res)
         {
-            if ((info = append_entry( buffer, io, length, de->d_name, NULL, mask, class )))
+            if (total >= sizeof(struct dirent))
             {
-                last_info = info;
-                if (!has_wildcard( mask )) break;
-                /* if we have to return but the buffer contains more data, restart with a smaller size */
-                if (res > 0 && (single_entry || io->Information + max_dir_info_size(class) > length))
-                {
-                    lseek( fd, (unsigned long)restart_pos, SEEK_SET );
-                    size = (char *)de + dir_reclen(de) - data;
-                    io->Information = restart_info_pos;
-                    last_info = restart_last_info;
-                    goto restart;
-                }
+                total = res = wine_getdirentries( fd, data, sizeof(data), &restart_pos );
+                de = (struct dirent *)data;
             }
             else
-            {
-                if (io->u.Status == STATUS_BUFFER_OVERFLOW)
-                {
-                    lseek( fd, (unsigned long)restart_pos, SEEK_SET );
-                    if (restart_info_pos)  /* if we have a complete read already, return it */
-                    {
-                        io->u.Status = STATUS_SUCCESS;
-                        io->Information = restart_info_pos;
-                        last_info = restart_last_info;
-                        break;
-                    }
-                    /* otherwise restart from the start with a smaller size */
-                    size = (char *)de - data;
-                    if (!size) break;
-                    io->Information = 0;
-                    last_info = NULL;
-                    goto restart;
-                }
-            }
-        }
-        /* move on to the next entry */
-        if (res > 0)
-        {
-            de = (struct dirent *)((char *)de + dir_reclen(de));
-            continue;
+                break;
         }
-        if (size < initial_size) break;  /* already restarted once, give up now */
-        restart_last_info = last_info;
-        restart_info_pos = io->Information;
-    restart:
-        res = wine_getdirentries( fd, data, size, &restart_pos );
-        de = (struct dirent *)data;
     }
 
+    /* Store the entry counter in the fd offset. */
+    lseek( fd, i, SEEK_SET );
     if (last_info)
         last_info->next = 0;
     else if (io->u.Status != STATUS_BUFFER_OVERFLOW)
         io->u.Status = restart_scan ? STATUS_NO_SUCH_FILE : STATUS_NO_MORE_FILES;
     res = 0;
 done:
-    if (data != local_buffer) RtlFreeHeap( GetProcessHeap(), 0, data );
     return res;
 }
 
-- 
2.4.6




More information about the wine-patches mailing list