[PATCH 2/2] ntdll: Use swapping method to return . and .. as first entries. (try 3)
Grazvydas Ignotas
notasas at gmail.com
Wed Jul 27 16:31:51 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.
---
dlls/ntdll/directory.c | 127 ++++++++++++++++++++++++++++--------------
dlls/ntdll/tests/directory.c | 13 +---
2 files changed, 88 insertions(+), 52 deletions(-)
diff --git a/dlls/ntdll/directory.c b/dlls/ntdll/directory.c
index 42b3639..0bb0805 100644
--- a/dlls/ntdll/directory.c
+++ b/dlls/ntdll/directory.c
@@ -295,6 +295,20 @@ static void flush_dir_queue(void)
}
}
+static char *ntdll_strdup(const char *s)
+{
+ char *r;
+ int len;
+
+ if (s)
+ {
+ len = strlen(s) + 1;
+ r = RtlAllocateHeap(GetProcessHeap(), 0, len);
+ if (r)
+ return memcpy(r, s, len);
+ }
+ return NULL;
+}
/***********************************************************************
* get_default_com_device
@@ -1486,22 +1500,58 @@ static int read_directory_vfat( int fd, IO_STATUS_BLOCK *io, void *buffer, ULONG
#endif /* VFAT_IOCTL_READDIR_BOTH */
+#ifdef USE_GETDENTS
+/***********************************************************************
+ * get_first_dent_names, read_first_dent_names
+ *
+ * gets or reads second directory offset and first two dentry names (if they have inodes).
+ */
+static void get_first_dent_names( const KERNEL_DIRENT64 *de, int size, off_t *second_entry_pos,
+ char **firstname, char **secondname )
+{
+ *second_entry_pos = de->d_off;
+ if (de->d_ino)
+ *firstname = ntdll_strdup(de->d_name);
+ if (size > de->d_reclen)
+ {
+ de = (const KERNEL_DIRENT64 *)((const char *)de + de->d_reclen);
+ if (de->d_ino)
+ *secondname = ntdll_strdup(de->d_name);
+ }
+}
+
+static void read_first_dent_names( int fd, off_t old_pos, off_t *second_entry_pos,
+ char **firstname, char **secondname )
+{
+ char buffer[4096];
+ int res;
+
+ lseek( fd, 0, SEEK_SET );
+ res = getdents64( fd, buffer, sizeof(buffer) );
+ if (res != -1)
+ get_first_dent_names( (KERNEL_DIRENT64 *)buffer, res, second_entry_pos, firstname, secondname );
+ lseek( fd, old_pos, SEEK_SET );
+}
+
/***********************************************************************
* 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 )
{
+ static off_t second_entry_pos;
+ static struct file_identity last_dir_id;
+ char *firstnames[2] = { NULL, NULL };
off_t old_pos = 0;
size_t size = length;
- int res, fake_dot_dot = 1;
char *data, local_buffer[8192];
KERNEL_DIRENT64 *de;
union file_directory_info *info, *last_info = NULL;
+ const char *filename;
+ int res, which;
if (size <= sizeof(local_buffer) || !(data = RtlAllocateHeap( GetProcessHeap(), 0, size )))
{
@@ -1509,8 +1559,7 @@ static int read_directory_getdents( int fd, IO_STATUS_BLOCK *io, void *buffer, U
data = local_buffer;
}
- 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 */
+ if (!restart_scan)
{
old_pos = lseek( fd, 0, SEEK_CUR );
if (old_pos == -1 && errno == ENOENT)
@@ -1536,50 +1585,42 @@ static int read_directory_getdents( int fd, IO_STATUS_BLOCK *io, void *buffer, U
de = (KERNEL_DIRENT64 *)data;
- if (restart_scan)
+ /* maintain second_entry_pos to know when to return '..' */
+ if (old_pos == 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;
- }
- }
-
- if (fake_dot_dot)
- {
- 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)
- {
- lseek( fd, 0, SEEK_SET ); /* reset pos to first entry */
- res = 0;
- }
- }
+ get_first_dent_names( de, res, &second_entry_pos, &firstnames[0], &firstnames[1] );
+ last_dir_id = curdir;
+ }
+ else if (last_dir_id.dev != curdir.dev || last_dir_id.ino != curdir.ino)
+ {
+ read_first_dent_names( fd, old_pos, &second_entry_pos, &firstnames[0], &firstnames[1] );
+ last_dir_id = curdir;
}
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 )))
+ filename = NULL;
+
+ /* 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 (!firstnames[0])
+ read_first_dent_names( fd, de->d_off, &second_entry_pos, &firstnames[0], &firstnames[1] );
+ which = !strcmp( de->d_name, "." ) ? 0 : 1;
+ if (firstnames[which] && (!strcmp( firstnames[which], "." ) || !strcmp( firstnames[which], ".." )))
+ which ^= 1;
+ filename = firstnames[which];
+ }
+ else if (de->d_ino)
+ filename = de->d_name;
+
+ if (filename && (info = append_entry( buffer, io, length, filename, NULL, mask, class )))
{
last_info = info;
if (io->u.Status == STATUS_BUFFER_OVERFLOW)
@@ -1608,6 +1649,8 @@ static int read_directory_getdents( int fd, IO_STATUS_BLOCK *io, void *buffer, U
else io->u.Status = restart_scan ? STATUS_NO_SUCH_FILE : STATUS_NO_MORE_FILES;
res = 0;
done:
+ if (firstnames[0]) RtlFreeHeap( GetProcessHeap(), 0, firstnames[0] );
+ if (firstnames[1]) RtlFreeHeap( GetProcessHeap(), 0, firstnames[1] );
if (data != local_buffer) RtlFreeHeap( GetProcessHeap(), 0, data );
return res;
}
diff --git a/dlls/ntdll/tests/directory.c b/dlls/ntdll/tests/directory.c
index 40ca0ac..11b1d0c 100644
--- a/dlls/ntdll/tests/directory.c
+++ b/dlls/ntdll/tests/directory.c
@@ -219,16 +219,9 @@ static void test_NtQueryDirectoryFile(BOOLEAN single_entry, BOOLEAN restart_flag
}
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);
done:
--
1.7.0.4
More information about the wine-patches
mailing list