From: Eugene McArdle eugene@tensorworks.com.au
--- dlls/ntdll/tests/directory.c | 145 +++++++++++++++++++++++++++++++++++ dlls/ntdll/unix/file.c | 56 +++++++++++++- 2 files changed, 199 insertions(+), 2 deletions(-)
diff --git a/dlls/ntdll/tests/directory.c b/dlls/ntdll/tests/directory.c index 52ff9936560..bb496301f12 100644 --- a/dlls/ntdll/tests/directory.c +++ b/dlls/ntdll/tests/directory.c @@ -964,6 +964,150 @@ done: pRtlFreeUnicodeString(&ntdirname); }
+static void set_up_mask_test(const char *testdir) +{ + BOOL ret; + char buf[MAX_PATH + 5]; + HANDLE h; + + ret = CreateDirectoryA(testdir, NULL); + ok( ret, "couldn't create dir '%s', error %ld\n", testdir, GetLastError() ); + + sprintf(buf, "%s\%s", testdir, "a-file.h"); + h = CreateFileA(buf, GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, 0); + ok( h != INVALID_HANDLE_VALUE, "failed to create temp file '%s'\n", buf ); + CloseHandle(h); + + sprintf(buf, "%s\%s", testdir, "another-file.h"); + h = CreateFileA(buf, GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, 0); + ok( h != INVALID_HANDLE_VALUE, "failed to create temp file '%s'\n", buf ); + CloseHandle(h); +} + +static void tear_down_mask_test(const char *testdir) +{ + int ret; + char buf[MAX_PATH]; + + sprintf(buf, "%s\%s", testdir, "a-file.h"); + ret = DeleteFileA(buf); + ok( ret || (GetLastError() == ERROR_FILE_NOT_FOUND) || (GetLastError() == ERROR_PATH_NOT_FOUND), + "Failed to rm %s, error %ld\n", buf, GetLastError() ); + + sprintf(buf, "%s\%s", testdir, "another-file.h"); + ret = DeleteFileA(buf); + ok( ret || (GetLastError() == ERROR_FILE_NOT_FOUND) || (GetLastError() == ERROR_PATH_NOT_FOUND), + "Failed to rm %s, error %ld\n", buf, GetLastError() ); + + RemoveDirectoryA(testdir); +} + + +// Performs a query with NtQueryDirectoryFile() +static void test_NtQueryDirectoryFile_mask(HANDLE handle, BOOL restart_scan, UNICODE_STRING mask, BOOL expect_success) +{ + NTSTATUS status; + IO_STATUS_BLOCK io; + UINT data_size = sizeof(FILE_DIRECTORY_INFORMATION) + (MAX_PATH * sizeof(wchar_t)); + BYTE data[8192]; + FILE_DIRECTORY_INFORMATION *dir_info; + WCHAR *name; + ULONG name_len; + ULONG mask_len = mask.Length / sizeof(WCHAR); + + // Perform the query + status = pNtQueryDirectoryFile( handle, NULL, NULL, NULL, &io, data, data_size, + FileDirectoryInformation, TRUE, &mask, restart_scan ); + if (expect_success) + { + ok( status == STATUS_SUCCESS, "could not find file mask: '%s', restart: %d, error %ld\n", + wine_dbgstr_wn(mask.Buffer, mask_len), restart_scan, GetLastError() ); + // Print the query parameters and the result + if (status == 0) { + dir_info = (FILE_DIRECTORY_INFORMATION *)data; + name = dir_info->FileName; + name_len = dir_info->FileNameLength / sizeof(WCHAR); + + ok( name_len == mask_len, "unexpected filename length %lu, expected %lu\n", name_len, mask_len ); + ok( !memcmp(name, mask.Buffer, mask_len), "unexpected filename %s, expected %s\n", + wine_dbgstr_wn(name, name_len), wine_dbgstr_wn(mask.Buffer, mask_len) ); + } + } + else + { + // Print the query parameters and the result + if (status == 0) { + dir_info = (FILE_DIRECTORY_INFORMATION *)data; + name = dir_info->FileName; + name_len = dir_info->FileNameLength / sizeof(WCHAR); + + ok ( status != STATUS_SUCCESS, "unexpectedly found file. mask: '%s', found %s\n", + wine_dbgstr_wn(mask.Buffer, mask_len), wine_dbgstr_wn(name, name_len) ); + } + } +} + +static void test_NtQueryDirectoryFile_change_mask(void) +{ + NTSTATUS status; + HANDLE dirh; + char testdir[MAX_PATH]; + OBJECT_ATTRIBUTES attr; + IO_STATUS_BLOCK io; + UNICODE_STRING ntdirname; + WCHAR testdir_w[MAX_PATH]; + + UNICODE_STRING atestfile; + UNICODE_STRING anothertestfile; + UNICODE_STRING notatestfile; + + pRtlInitUnicodeString(&atestfile, L"a-file.h"); + pRtlInitUnicodeString(&anothertestfile, L"another-file.h"); + pRtlInitUnicodeString(¬atestfile, L"not-a-file.h"); + + /* Clean up from prior aborted run, if any, then set up test files */ + ok( GetTempPathA(MAX_PATH, testdir), "couldn't get temp dir\n" ); + strcat(testdir, "mask.tmp"); + tear_down_mask_test(testdir); + set_up_mask_test(testdir); + + pRtlMultiByteToUnicodeN(testdir_w, sizeof(testdir_w), NULL, testdir, strlen(testdir) + 1); + if (!pRtlDosPathNameToNtPathName_U(testdir_w, &ntdirname, NULL, NULL)) + { + ok( 0, "RtlDosPathNametoNtPathName_U failed\n" ); + goto done; + } + InitializeObjectAttributes(&attr, &ntdirname, OBJ_CASE_INSENSITIVE, 0, NULL); + + // Open a handle for our test directory + status = pNtOpenFile(&dirh, SYNCHRONIZE | FILE_LIST_DIRECTORY, &attr, &io, FILE_SHARE_READ, + FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT | FILE_DIRECTORY_FILE); + ok( status == STATUS_SUCCESS, "failed to open dir '%s', ret 0x%lx, error %ld\n", testdir, status, GetLastError() ); + if (status != STATUS_SUCCESS) + { + skip("can't test if we can't open the directory\n"); + return; + } + + // Run tests - all searches for `notatestfile` are expected to fail + test_NtQueryDirectoryFile_mask(dirh, TRUE, atestfile, TRUE); + test_NtQueryDirectoryFile_mask(dirh, TRUE, anothertestfile, TRUE); + test_NtQueryDirectoryFile_mask(dirh, TRUE, notatestfile, FALSE); + + test_NtQueryDirectoryFile_mask(dirh, TRUE, atestfile, TRUE); + test_NtQueryDirectoryFile_mask(dirh, FALSE, notatestfile, FALSE); + + test_NtQueryDirectoryFile_mask(dirh, TRUE, atestfile, TRUE); + // Since we are not resetting the scan, and are using an incompatible mask, this should also fail + test_NtQueryDirectoryFile_mask(dirh, FALSE, anothertestfile, FALSE); + + // Cleanup +done: + tear_down_mask_test(testdir); +} + static NTSTATUS get_file_id( FILE_INTERNAL_INFORMATION *info, const WCHAR *root, const WCHAR *name ) { OBJECT_ATTRIBUTES attr; @@ -1157,5 +1301,6 @@ START_TEST(directory) test_directory_sort( sysdir ); test_NtQueryDirectoryFile(); test_NtQueryDirectoryFile_case(); + test_NtQueryDirectoryFile_change_mask(); test_redirection(); } diff --git a/dlls/ntdll/unix/file.c b/dlls/ntdll/unix/file.c index 9da53339ff0..3888feb2792 100644 --- a/dlls/ntdll/unix/file.c +++ b/dlls/ntdll/unix/file.c @@ -225,6 +225,7 @@ struct dir_data struct file_identity id; /* directory file identity */ struct dir_data_names *names; /* directory file names */ struct dir_data_buffer *buffer; /* head of data buffers list */ + UNICODE_STRING mask; /* the mask used when creating the cache entry */ };
static const unsigned int dir_data_buffer_initial_size = 4096; @@ -565,6 +566,7 @@ static void free_dir_data( struct dir_data *data ) free( buffer ); } free( data->names ); + if (data->mask.Buffer) free( data->mask.Buffer ); free( data ); }
@@ -2606,6 +2608,14 @@ static NTSTATUS init_cached_dir_data( struct dir_data **data_ret, int fd, const return status; }
+ /* if a mask was specified then copy it into the cache entry */ + if (mask && mask->Length) + { + data->mask.Length = data->mask.MaximumLength = mask->Length; + if (!(data->mask.Buffer = calloc( 1, mask->Length ))) return STATUS_NO_MEMORY; + memcpy(data->mask.Buffer, mask->Buffer, mask->Length); + } + /* sort filenames, but not "." and ".." */ i = 0; if (i < data->count && !strcmp( data->names[i].unix_name, "." )) i++; @@ -2628,17 +2638,35 @@ static NTSTATUS init_cached_dir_data( struct dir_data **data_ret, int fd, const }
+/*********************************************************************** + * ustring_equal + * + * Simplified version of RtlEqualUnicodeString that performs only case-sensitive comparisons. + */ +static BOOLEAN ustring_equal( const UNICODE_STRING *a, const UNICODE_STRING *b ) +{ + USHORT length_a = (a ? a->Length : 0); + USHORT length_b = (b ? b->Length : 0); + if (length_a != length_b) return FALSE; + + if (length_a == 0 && length_b == 0) return TRUE; + return (!memcmp(a->Buffer, b->Buffer, a->Length)); +} + + /*********************************************************************** * get_cached_dir_data * * Retrieve the cached directory data, or initialize it if necessary. */ static unsigned int get_cached_dir_data( HANDLE handle, struct dir_data **data_ret, int fd, - const UNICODE_STRING *mask ) + const UNICODE_STRING *mask, BOOLEAN restart_scan, + BOOLEAN *fresh_handle ) { unsigned int i; int entry = -1, free_entries[16]; unsigned int status; + *fresh_handle = TRUE;
SERVER_START_REQ( get_directory_cache_entry ) { @@ -2675,6 +2703,22 @@ static unsigned int get_cached_dir_data( HANDLE handle, struct dir_data **data_r dir_data_cache_size = size; }
+ // If we have an existing cache entry then set the flag to indicate that the handle is not fresh + if (dir_data_cache[entry]) *fresh_handle = FALSE; + + /* + If we have an existing cache entry, but restart_scan is true and a new non-empty mask + was specified then we need to invalidate the existing cache entry and create a new one + */ + if (dir_data_cache[entry] && restart_scan && mask && + !ustring_equal(&dir_data_cache[entry]->mask, mask)) + { + TRACE( "invalidating existing cache entry for handle %p, old mask: "%s", new mask: "%s"\n", + handle, debugstr_us(&(dir_data_cache[entry]->mask)), debugstr_us(mask)); + free_dir_data( dir_data_cache[entry] ); + dir_data_cache[entry] = NULL; + } + if (!dir_data_cache[entry]) status = init_cached_dir_data( &dir_data_cache[entry], fd, mask );
*data_ret = dir_data_cache[entry]; @@ -2694,6 +2738,7 @@ NTSTATUS WINAPI NtQueryDirectoryFile( HANDLE handle, HANDLE event, PIO_APC_ROUTI enum server_fd_type type; struct dir_data *data; unsigned int status; + BOOLEAN fresh_handle = TRUE;
TRACE("(%p %p %p %p %p %p 0x%08x 0x%08x 0x%08x %s 0x%08x\n", handle, event, apc_routine, apc_context, io, buffer, @@ -2746,7 +2791,7 @@ NTSTATUS WINAPI NtQueryDirectoryFile( HANDLE handle, HANDLE event, PIO_APC_ROUTI cwd = open( ".", O_RDONLY ); if (fchdir( fd ) != -1) { - if (!(status = get_cached_dir_data( handle, &data, fd, mask ))) + if (!(status = get_cached_dir_data( handle, &data, fd, mask, restart_scan, &fresh_handle ))) { union file_directory_info *last_info = NULL;
@@ -2768,6 +2813,13 @@ NTSTATUS WINAPI NtQueryDirectoryFile( HANDLE handle, HANDLE event, PIO_APC_ROUTI } else status = errno_to_status( errno );
+ // Return STATUS_NO_MORE_FILES if a mask did not match a file and the handle had previously been used + if (status == STATUS_NO_SUCH_FILE && !fresh_handle) + { + status = STATUS_NO_MORE_FILES; + io->Status = status; + } + mutex_unlock( &dir_mutex );
if (needs_close) close( fd );