From 006413afe495fbf73542154462885e4fdbf12a2a Mon Sep 17 00:00:00 2001 From: Grazvydas Ignotas Date: Thu, 4 Aug 2011 23:24:49 +0300 Subject: [PATCH] ntdll: Use swapping method to return . and .. as first entries. --- dlls/ntdll/directory.c | 138 +++++++++++++++++++++++------------ dlls/ntdll/tests/directory.c | 13 +--- 2 files changed, 94 insertions(+), 57 deletions(-) diff --git a/dlls/ntdll/directory.c b/dlls/ntdll/directory.c index 42b36393710..548189ca6b3 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 (restart_scan) + if (old_pos == 0 && 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; - } - } - - 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; - } - } + second_entry_pos = de->d_off; + last_dir_id = curdir; + if (res > de->d_reclen) + de_first_two = de; } 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 ))) + next_pos = de->d_off; + 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, ".." )) + { + 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, next_pos, SEEK_SET ); + res = 0; + } + } + 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) @@ -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 b32c22aae11..c74bf341a21 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); }