kernel32: Added more ReadDirectoryChangesW tests.
[wine] / dlls / kernel32 / tests / change.c
1 /*
2  * Tests for file change notification functions
3  *
4  * Copyright (c) 2004 Hans Leidekker
5  * Copyright 2006 Mike McCormack for CodeWeavers
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21
22 /* TODO: - security attribute changes
23  *       - compound filter and multiple notifications
24  *       - subtree notifications
25  *       - non-documented flags FILE_NOTIFY_CHANGE_LAST_ACCESS and
26  *         FILE_NOTIFY_CHANGE_CREATION
27  */
28
29 #include <stdarg.h>
30 #include <stdio.h>
31
32 #include "ntstatus.h"
33 #define WIN32_NO_STATUS
34 #include "wine/test.h"
35 #include <windef.h>
36 #include <winbase.h>
37 #include <winternl.h>
38
39 static DWORD CALLBACK NotificationThread(LPVOID arg)
40 {
41     HANDLE change = arg;
42     BOOL notified = FALSE;
43     BOOL ret = FALSE;
44     DWORD status;
45
46     status = WaitForSingleObject(change, 100);
47
48     if (status == WAIT_OBJECT_0 ) {
49         notified = TRUE;
50         ret = FindNextChangeNotification(change);
51     }
52
53     ret = FindCloseChangeNotification(change);
54     ok( ret, "FindCloseChangeNotification error: %d\n",
55        GetLastError());
56
57     ExitThread((DWORD)notified);
58 }
59
60 static HANDLE StartNotificationThread(LPCSTR path, BOOL subtree, DWORD flags)
61 {
62     HANDLE change, thread;
63     DWORD threadId;
64
65     change = FindFirstChangeNotificationA(path, subtree, flags);
66     ok(change != INVALID_HANDLE_VALUE, "FindFirstChangeNotification error: %d\n", GetLastError());
67
68     thread = CreateThread(NULL, 0, NotificationThread, change, 0, &threadId);
69     ok(thread != NULL, "CreateThread error: %d\n", GetLastError());
70
71     return thread;
72 }
73
74 static DWORD FinishNotificationThread(HANDLE thread)
75 {
76     DWORD status, exitcode;
77
78     status = WaitForSingleObject(thread, 5000);
79     ok(status == WAIT_OBJECT_0, "WaitForSingleObject status %d error %d\n", status, GetLastError());
80
81     ok(GetExitCodeThread(thread, &exitcode), "Could not retrieve thread exit code\n");
82     CloseHandle(thread);
83
84     return exitcode;
85 }
86
87 static void test_FindFirstChangeNotification(void)
88 {
89     HANDLE change, file, thread;
90     DWORD attributes, count;
91     BOOL ret;
92
93     char workdir[MAX_PATH], dirname1[MAX_PATH], dirname2[MAX_PATH];
94     char filename1[MAX_PATH], filename2[MAX_PATH];
95     static const char prefix[] = "FCN";
96     char buffer[2048];
97
98     /* pathetic checks */
99
100     change = FindFirstChangeNotificationA("not-a-file", FALSE, FILE_NOTIFY_CHANGE_FILE_NAME);
101     ok(change == INVALID_HANDLE_VALUE, "Expected INVALID_HANDLE_VALUE, got %p\n", change);
102     ok(GetLastError() == ERROR_FILE_NOT_FOUND ||
103        GetLastError() == ERROR_NO_MORE_FILES, /* win95 */
104        "FindFirstChangeNotification error: %d\n", GetLastError());
105
106     if (0) /* This documents win2k behavior. It crashes on win98. */
107     { 
108         change = FindFirstChangeNotificationA(NULL, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME);
109         ok(change == NULL && GetLastError() == ERROR_PATH_NOT_FOUND,
110         "FindFirstChangeNotification error: %d\n", GetLastError());
111     }
112
113     ret = FindNextChangeNotification(NULL);
114     ok(!ret && GetLastError() == ERROR_INVALID_HANDLE, "FindNextChangeNotification error: %d\n",
115        GetLastError());
116
117     ret = FindCloseChangeNotification(NULL);
118     ok(!ret && GetLastError() == ERROR_INVALID_HANDLE, "FindCloseChangeNotification error: %d\n",
119        GetLastError());
120
121     ret = GetTempPathA(MAX_PATH, workdir);
122     ok(ret, "GetTempPathA error: %d\n", GetLastError());
123
124     lstrcatA(workdir, "testFileChangeNotification");
125
126     ret = CreateDirectoryA(workdir, NULL);
127     ok(ret, "CreateDirectoryA error: %d\n", GetLastError());
128
129     ret = GetTempFileNameA(workdir, prefix, 0, filename1);
130     ok(ret, "GetTempFileNameA error: %d\n", GetLastError());
131
132     file = CreateFileA(filename1, GENERIC_WRITE|GENERIC_READ, 0, NULL, CREATE_ALWAYS,
133                        FILE_ATTRIBUTE_NORMAL, 0);
134     ok(file != INVALID_HANDLE_VALUE, "CreateFileA error: %d\n", GetLastError());
135     ret = CloseHandle(file);
136     ok( ret, "CloseHandle error: %d\n", GetLastError());
137
138     /* Try to register notification for a file. win98 and win2k behave differently here */
139     change = FindFirstChangeNotificationA(filename1, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME);
140     ok(change == INVALID_HANDLE_VALUE && (GetLastError() == ERROR_DIRECTORY ||
141                                           GetLastError() == ERROR_FILE_NOT_FOUND),
142        "FindFirstChangeNotification error: %d\n", GetLastError());
143
144     lstrcpyA(dirname1, filename1);
145     lstrcatA(dirname1, "dir");
146
147     lstrcpyA(dirname2, dirname1);
148     lstrcatA(dirname2, "new");
149
150     ret = CreateDirectoryA(dirname1, NULL);
151     ok(ret, "CreateDirectoryA error: %d\n", GetLastError());
152
153     /* What if we move the directory we registered notification for? */
154     thread = StartNotificationThread(dirname1, FALSE, FILE_NOTIFY_CHANGE_DIR_NAME);
155     ret = MoveFileA(dirname1, dirname2);
156     ok(ret, "MoveFileA error: %d\n", GetLastError());
157     /* win9x and win2k behave differently here, don't check result */
158     FinishNotificationThread(thread);
159
160     /* What if we remove the directory we registered notification for? */
161     thread = StartNotificationThread(dirname2, FALSE, FILE_NOTIFY_CHANGE_DIR_NAME);
162     ret = RemoveDirectoryA(dirname2);
163     ok(ret, "RemoveDirectoryA error: %d\n", GetLastError());
164     /* win9x and win2k behave differently here, don't check result */
165     FinishNotificationThread(thread);
166
167     /* functional checks */
168
169     /* Create a directory */
170     thread = StartNotificationThread(workdir, FALSE, FILE_NOTIFY_CHANGE_DIR_NAME);
171     ret = CreateDirectoryA(dirname1, NULL);
172     ok(ret, "CreateDirectoryA error: %d\n", GetLastError());
173     ok(FinishNotificationThread(thread), "Missed notification\n");
174
175     /* Rename a directory */
176     thread = StartNotificationThread(workdir, FALSE, FILE_NOTIFY_CHANGE_DIR_NAME);
177     ret = MoveFileA(dirname1, dirname2);
178     ok(ret, "MoveFileA error: %d\n", GetLastError());
179     ok(FinishNotificationThread(thread), "Missed notification\n");
180
181     /* Delete a directory */
182     thread = StartNotificationThread(workdir, FALSE, FILE_NOTIFY_CHANGE_DIR_NAME);
183     ret = RemoveDirectoryA(dirname2);
184     ok(ret, "RemoveDirectoryA error: %d\n", GetLastError());
185     ok(FinishNotificationThread(thread), "Missed notification\n");
186
187     lstrcpyA(filename2, filename1);
188     lstrcatA(filename2, "new");
189
190     /* Rename a file */
191     thread = StartNotificationThread(workdir, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME);
192     ret = MoveFileA(filename1, filename2);
193     ok(ret, "MoveFileA error: %d\n", GetLastError());
194     ok(FinishNotificationThread(thread), "Missed notification\n");
195
196     /* Delete a file */
197     thread = StartNotificationThread(workdir, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME);
198     ret = DeleteFileA(filename2);
199     ok(ret, "DeleteFileA error: %d\n", GetLastError());
200     ok(FinishNotificationThread(thread), "Missed notification\n");
201
202     /* Create a file */
203     thread = StartNotificationThread(workdir, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME);
204     file = CreateFileA(filename2, GENERIC_WRITE|GENERIC_READ, 0, NULL, CREATE_ALWAYS, 
205                        FILE_ATTRIBUTE_NORMAL, 0);
206     ok(file != INVALID_HANDLE_VALUE, "CreateFileA error: %d\n", GetLastError());
207     ret = CloseHandle(file);
208     ok( ret, "CloseHandle error: %d\n", GetLastError());
209     ok(FinishNotificationThread(thread), "Missed notification\n");
210
211     attributes = GetFileAttributesA(filename2);
212     ok(attributes != INVALID_FILE_ATTRIBUTES, "GetFileAttributesA error: %d\n", GetLastError());
213     attributes &= FILE_ATTRIBUTE_READONLY;
214
215     /* Change file attributes */
216     thread = StartNotificationThread(workdir, FALSE, FILE_NOTIFY_CHANGE_ATTRIBUTES);
217     ret = SetFileAttributesA(filename2, attributes);
218     ok(ret, "SetFileAttributesA error: %d\n", GetLastError());
219     ok(FinishNotificationThread(thread), "Missed notification\n");
220
221     /* Change last write time by writing to a file */
222     thread = StartNotificationThread(workdir, FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE);
223     file = CreateFileA(filename2, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 
224                        FILE_ATTRIBUTE_NORMAL, 0);
225     ok(file != INVALID_HANDLE_VALUE, "CreateFileA error: %d\n", GetLastError());
226     memset(buffer, 0, sizeof(buffer));
227     ret = WriteFile(file, buffer, sizeof(buffer), &count, NULL);
228     ok(ret && count == sizeof(buffer), "WriteFile error: %d\n", GetLastError());
229     ret = CloseHandle(file);
230     ok( ret, "CloseHandle error: %d\n", GetLastError());
231     ok(FinishNotificationThread(thread), "Missed notification\n");
232
233     /* Change file size by truncating a file */
234     thread = StartNotificationThread(workdir, FALSE, FILE_NOTIFY_CHANGE_SIZE);
235     file = CreateFileA(filename2, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 
236                        FILE_ATTRIBUTE_NORMAL, 0);
237     ok(file != INVALID_HANDLE_VALUE, "CreateFileA error: %d\n", GetLastError());
238     ret = WriteFile(file, buffer, sizeof(buffer) / 2, &count, NULL);
239     ok(ret && count == sizeof(buffer) / 2, "WriteFileA error: %d\n", GetLastError());
240     ret = CloseHandle(file);
241     ok( ret, "CloseHandle error: %d\n", GetLastError());
242     ok(FinishNotificationThread(thread), "Missed notification\n");
243
244     /* clean up */
245     
246     ret = DeleteFileA(filename2);
247     ok(ret, "DeleteFileA error: %d\n", GetLastError());
248
249     ret = RemoveDirectoryA(workdir);
250     ok(ret, "RemoveDirectoryA error: %d\n", GetLastError());
251 }
252
253 /* this test concentrates more on the wait behaviour of the handle */
254 static void test_ffcn(void)
255 {
256     DWORD filter;
257     HANDLE handle;
258     LONG r;
259     WCHAR path[MAX_PATH], subdir[MAX_PATH];
260     static const WCHAR szBoo[] = { '\\','b','o','o',0 };
261     static const WCHAR szHoo[] = { '\\','h','o','o',0 };
262
263     SetLastError(0xdeadbeef);
264     r = GetTempPathW( MAX_PATH, path );
265     if (!r && (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED))
266     {
267         win_skip("GetTempPathW is not implemented\n");
268         return;
269     }
270     ok( r != 0, "temp path failed\n");
271     if (!r)
272         return;
273
274     lstrcatW( path, szBoo );
275     lstrcpyW( subdir, path );
276     lstrcatW( subdir, szHoo );
277
278     RemoveDirectoryW( subdir );
279     RemoveDirectoryW( path );
280     
281     r = CreateDirectoryW(path, NULL);
282     ok( r == TRUE, "failed to create directory\n");
283
284     filter = FILE_NOTIFY_CHANGE_FILE_NAME;
285     filter |= FILE_NOTIFY_CHANGE_DIR_NAME;
286
287     handle = FindFirstChangeNotificationW( path, 1, filter);
288     ok( handle != INVALID_HANDLE_VALUE, "invalid handle\n");
289
290     r = WaitForSingleObject( handle, 0 );
291     ok( r == STATUS_TIMEOUT, "should time out\n");
292
293     r = CreateDirectoryW( subdir, NULL );
294     ok( r == TRUE, "failed to create subdir\n");
295
296     r = WaitForSingleObject( handle, 0 );
297     ok( r == WAIT_OBJECT_0, "should be ready\n");
298
299     r = WaitForSingleObject( handle, 0 );
300     ok( r == WAIT_OBJECT_0, "should be ready\n");
301
302     r = FindNextChangeNotification(handle);
303     ok( r == TRUE, "find next failed\n");
304
305     r = WaitForSingleObject( handle, 0 );
306     ok( r == STATUS_TIMEOUT, "should time out\n");
307
308     r = RemoveDirectoryW( subdir );
309     ok( r == TRUE, "failed to remove subdir\n");
310
311     r = WaitForSingleObject( handle, 0 );
312     ok( r == WAIT_OBJECT_0, "should be ready\n");
313
314     r = WaitForSingleObject( handle, 0 );
315     ok( r == WAIT_OBJECT_0, "should be ready\n");
316
317     r = FindNextChangeNotification(handle);
318     ok( r == TRUE, "find next failed\n");
319
320     r = FindNextChangeNotification(handle);
321     ok( r == TRUE, "find next failed\n");
322
323     r = FindCloseChangeNotification(handle);
324     ok( r == TRUE, "should succeed\n");
325
326     r = RemoveDirectoryW( path );
327     ok( r == TRUE, "failed to remove dir\n");
328 }
329
330 /* this test concentrates on the wait behavior when multiple threads are
331  * waiting on a change notification handle. */
332 static void test_ffcnMultipleThreads(void)
333 {
334     LONG r;
335     DWORD filter, threadId, status, exitcode;
336     HANDLE handles[2];
337     char path[MAX_PATH];
338
339     r = GetTempPathA(MAX_PATH, path);
340     ok(r, "GetTempPathA error: %d\n", GetLastError());
341
342     lstrcatA(path, "ffcnTestMultipleThreads");
343
344     RemoveDirectoryA(path);
345
346     r = CreateDirectoryA(path, NULL);
347     ok(r, "CreateDirectoryA error: %d\n", GetLastError());
348
349     filter = FILE_NOTIFY_CHANGE_FILE_NAME;
350     filter |= FILE_NOTIFY_CHANGE_DIR_NAME;
351
352     handles[0] = FindFirstChangeNotificationA(path, FALSE, filter);
353     ok(handles[0] != INVALID_HANDLE_VALUE, "FindFirstChangeNotification error: %d\n", GetLastError());
354
355     /* Test behavior if a waiting thread holds the last reference to a change
356      * directory object with an empty wine user APC queue for this thread (bug #7286) */
357
358     /* Create our notification thread */
359     handles[1] = CreateThread(NULL, 0, NotificationThread, handles[0], 0,
360                               &threadId);
361     ok(handles[1] != NULL, "CreateThread error: %d\n", GetLastError());
362
363     status = WaitForMultipleObjects(2, handles, FALSE, 5000);
364     ok(status == WAIT_OBJECT_0 || status == WAIT_OBJECT_0+1, "WaitForMultipleObjects status %d error %d\n", status, GetLastError());
365     ok(GetExitCodeThread(handles[1], &exitcode), "Could not retrieve thread exit code\n");
366
367     /* Clean up */
368     r = RemoveDirectoryA( path );
369     ok( r == TRUE, "failed to remove dir\n");
370 }
371
372 static BOOL (WINAPI *pReadDirectoryChangesW)(HANDLE,LPVOID,DWORD,BOOL,DWORD,
373                          LPDWORD,LPOVERLAPPED,LPOVERLAPPED_COMPLETION_ROUTINE);
374
375 static void test_readdirectorychanges(void)
376 {
377     HANDLE hdir;
378     char buffer[0x1000];
379     DWORD fflags, filter = 0, r, dwCount;
380     OVERLAPPED ov;
381     WCHAR path[MAX_PATH], subdir[MAX_PATH], subsubdir[MAX_PATH];
382     static const WCHAR szBoo[] = { '\\','b','o','o',0 };
383     static const WCHAR szHoo[] = { '\\','h','o','o',0 };
384     static const WCHAR szGa[] = { '\\','h','o','o','\\','g','a',0 };
385     PFILE_NOTIFY_INFORMATION pfni;
386
387     if (!pReadDirectoryChangesW)
388     {
389         win_skip("ReadDirectoryChangesW is not available\n");
390         return;
391     }
392
393     SetLastError(0xdeadbeef);
394     r = GetTempPathW( MAX_PATH, path );
395     if (!r && (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED))
396     {
397         win_skip("GetTempPathW is not implemented\n");
398         return;
399     }
400     ok( r != 0, "temp path failed\n");
401     if (!r)
402         return;
403
404     lstrcatW( path, szBoo );
405     lstrcpyW( subdir, path );
406     lstrcatW( subdir, szHoo );
407
408     lstrcpyW( subsubdir, path );
409     lstrcatW( subsubdir, szGa );
410
411     RemoveDirectoryW( subsubdir );
412     RemoveDirectoryW( subdir );
413     RemoveDirectoryW( path );
414     
415     r = CreateDirectoryW(path, NULL);
416     ok( r == TRUE, "failed to create directory\n");
417
418     SetLastError(0xd0b00b00);
419     r = pReadDirectoryChangesW(NULL,NULL,0,FALSE,0,NULL,NULL,NULL);
420     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
421     ok(r==FALSE, "should return false\n");
422
423     fflags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
424     hdir = CreateFileW(path, GENERIC_READ|SYNCHRONIZE|FILE_LIST_DIRECTORY, 
425                         FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, 
426                         OPEN_EXISTING, fflags, NULL);
427     ok( hdir != INVALID_HANDLE_VALUE, "failed to open directory\n");
428
429     ov.hEvent = CreateEvent( NULL, 1, 0, NULL );
430
431     SetLastError(0xd0b00b00);
432     r = pReadDirectoryChangesW(hdir,NULL,0,FALSE,0,NULL,NULL,NULL);
433     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
434     ok(r==FALSE, "should return false\n");
435
436     SetLastError(0xd0b00b00);
437     r = pReadDirectoryChangesW(hdir,NULL,0,FALSE,0,NULL,&ov,NULL);
438     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
439     ok(r==FALSE, "should return false\n");
440
441     filter = FILE_NOTIFY_CHANGE_FILE_NAME;
442     filter |= FILE_NOTIFY_CHANGE_DIR_NAME;
443     filter |= FILE_NOTIFY_CHANGE_ATTRIBUTES;
444     filter |= FILE_NOTIFY_CHANGE_SIZE;
445     filter |= FILE_NOTIFY_CHANGE_LAST_WRITE;
446     filter |= FILE_NOTIFY_CHANGE_LAST_ACCESS;
447     filter |= FILE_NOTIFY_CHANGE_CREATION;
448     filter |= FILE_NOTIFY_CHANGE_SECURITY;
449
450     SetLastError(0xd0b00b00);
451     ov.Internal = 0;
452     ov.InternalHigh = 0;
453     memset( buffer, 0, sizeof buffer );
454
455     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,-1,NULL,&ov,NULL);
456     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
457     ok(r==FALSE, "should return false\n");
458
459     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,0,NULL,&ov,NULL);
460     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
461     ok(r==FALSE, "should return false\n");
462
463     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,TRUE,filter,NULL,&ov,NULL);
464     ok(r==TRUE, "should return true\n");
465
466     r = WaitForSingleObject( ov.hEvent, 10 );
467     ok( r == STATUS_TIMEOUT, "should timeout\n" );
468
469     r = CreateDirectoryW( subdir, NULL );
470     ok( r == TRUE, "failed to create directory\n");
471
472     r = WaitForSingleObject( ov.hEvent, 1000 );
473     ok( r == WAIT_OBJECT_0, "event should be ready\n" );
474
475     ok( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
476     ok( ov.InternalHigh == 0x12, "ov.InternalHigh wrong\n");
477
478     pfni = (PFILE_NOTIFY_INFORMATION) buffer;
479     ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
480     ok( pfni->Action == FILE_ACTION_ADDED, "action wrong\n" );
481     ok( pfni->FileNameLength == 6, "len wrong\n" );
482     ok( !memcmp(pfni->FileName,&szHoo[1],6), "name wrong\n" );
483
484     ResetEvent(ov.hEvent);
485     SetLastError(0xd0b00b00);
486     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,0,NULL,NULL,NULL);
487     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
488     ok(r==FALSE, "should return false\n");
489
490     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,0,NULL,&ov,NULL);
491     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
492     ok(r==FALSE, "should return false\n");
493
494     filter = FILE_NOTIFY_CHANGE_SIZE;
495
496     SetEvent(ov.hEvent);
497     ov.Internal = 1;
498     ov.InternalHigh = 1;
499     S(U(ov)).Offset = 0;
500     S(U(ov)).OffsetHigh = 0;
501     memset( buffer, 0, sizeof buffer );
502     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
503     ok(r==TRUE, "should return true\n");
504
505     ok( ov.Internal == STATUS_PENDING, "ov.Internal wrong\n");
506     ok( ov.InternalHigh == 1, "ov.InternalHigh wrong\n");
507
508     r = WaitForSingleObject( ov.hEvent, 0 );
509     ok( r == STATUS_TIMEOUT, "should timeout\n" );
510
511     r = RemoveDirectoryW( subdir );
512     ok( r == TRUE, "failed to remove directory\n");
513
514     r = WaitForSingleObject( ov.hEvent, 1000 );
515     ok( r == WAIT_OBJECT_0, "should be ready\n" );
516
517     ok( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
518     ok( ov.InternalHigh == 0x12, "ov.InternalHigh wrong\n");
519
520     if (ov.Internal == STATUS_SUCCESS)
521     {
522         r = GetOverlappedResult( hdir, &ov, &dwCount, TRUE );
523         ok( r == TRUE, "getoverlappedresult failed\n");
524         ok( dwCount == 0x12, "count wrong\n");
525     }
526
527     pfni = (PFILE_NOTIFY_INFORMATION) buffer;
528     ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
529     ok( pfni->Action == FILE_ACTION_REMOVED, "action wrong\n" );
530     ok( pfni->FileNameLength == 6, "len wrong\n" );
531     ok( !memcmp(pfni->FileName,&szHoo[1],6), "name wrong\n" );
532
533     /* what happens if the buffer is too small? */
534     r = pReadDirectoryChangesW(hdir,buffer,0x10,FALSE,filter,NULL,&ov,NULL);
535     ok(r==TRUE, "should return true\n");
536
537     r = CreateDirectoryW( subdir, NULL );
538     ok( r == TRUE, "failed to create directory\n");
539
540     r = WaitForSingleObject( ov.hEvent, 1000 );
541     ok( r == WAIT_OBJECT_0, "should be ready\n" );
542
543     ok( ov.Internal == STATUS_NOTIFY_ENUM_DIR, "ov.Internal wrong\n");
544     ok( ov.InternalHigh == 0, "ov.InternalHigh wrong\n");
545
546     /* test the recursive watch */
547     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
548     ok(r==TRUE, "should return true\n");
549
550     r = CreateDirectoryW( subsubdir, NULL );
551     ok( r == TRUE, "failed to create directory\n");
552
553     r = WaitForSingleObject( ov.hEvent, 1000 );
554     ok( r == WAIT_OBJECT_0, "should be ready\n" );
555
556     ok( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
557     ok( ov.InternalHigh == 0x18, "ov.InternalHigh wrong\n");
558
559     pfni = (PFILE_NOTIFY_INFORMATION) buffer;
560     ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
561     ok( pfni->Action == FILE_ACTION_ADDED, "action wrong\n" );
562     ok( pfni->FileNameLength == 6*sizeof(WCHAR), "len wrong\n" );
563     ok( !memcmp(pfni->FileName,&szGa[1],6*sizeof(WCHAR)), "name wrong\n" );
564
565     r = RemoveDirectoryW( subsubdir );
566     ok( r == TRUE, "failed to remove directory\n");
567
568     ov.Internal = 1;
569     ov.InternalHigh = 1;
570     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
571     ok(r==TRUE, "should return true\n");
572
573     r = RemoveDirectoryW( subdir );
574     ok( r == TRUE, "failed to remove directory\n");
575
576     r = WaitForSingleObject( ov.hEvent, 1000 );
577     ok( r == WAIT_OBJECT_0, "should be ready\n" );
578
579     pfni = (PFILE_NOTIFY_INFORMATION) buffer;
580     /* we may get a notification for the parent dir too */
581     if (pfni->Action == FILE_ACTION_MODIFIED && pfni->NextEntryOffset)
582     {
583         ok( pfni->FileNameLength == 3*sizeof(WCHAR), "len wrong %u\n", pfni->FileNameLength );
584         ok( !memcmp(pfni->FileName,&szGa[1],3*sizeof(WCHAR)), "name wrong\n" );
585         pfni = (PFILE_NOTIFY_INFORMATION)((char *)pfni + pfni->NextEntryOffset);
586     }
587     ok( pfni->NextEntryOffset == 0, "offset wrong %u\n", pfni->NextEntryOffset );
588     ok( pfni->Action == FILE_ACTION_REMOVED, "action wrong %u\n", pfni->Action );
589     ok( pfni->FileNameLength == 6*sizeof(WCHAR), "len wrong %u\n", pfni->FileNameLength );
590     ok( !memcmp(pfni->FileName,&szGa[1],6*sizeof(WCHAR)), "name wrong\n" );
591
592     ok( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
593     dwCount = (char *)&pfni->FileName[pfni->FileNameLength/sizeof(WCHAR)] - buffer;
594     ok( ov.InternalHigh == dwCount, "ov.InternalHigh wrong %lu/%u\n",ov.InternalHigh, dwCount );
595
596     CloseHandle(hdir);
597
598     r = RemoveDirectoryW( path );
599     ok( r == TRUE, "failed to remove directory\n");
600 }
601
602 /* show the behaviour when a null buffer is passed */
603 static void test_readdirectorychanges_null(void)
604 {
605     NTSTATUS r;
606     HANDLE hdir;
607     char buffer[0x1000];
608     DWORD fflags, filter = 0;
609     OVERLAPPED ov;
610     WCHAR path[MAX_PATH], subdir[MAX_PATH];
611     static const WCHAR szBoo[] = { '\\','b','o','o',0 };
612     static const WCHAR szHoo[] = { '\\','h','o','o',0 };
613     PFILE_NOTIFY_INFORMATION pfni;
614
615     if (!pReadDirectoryChangesW)
616     {
617         win_skip("ReadDirectoryChangesW is not available\n");
618         return;
619     }
620     SetLastError(0xdeadbeef);
621     r = GetTempPathW( MAX_PATH, path );
622     if (!r && (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED))
623     {
624         win_skip("GetTempPathW is not implemented\n");
625         return;
626     }
627     ok( r != 0, "temp path failed\n");
628     if (!r)
629         return;
630
631     lstrcatW( path, szBoo );
632     lstrcpyW( subdir, path );
633     lstrcatW( subdir, szHoo );
634
635     RemoveDirectoryW( subdir );
636     RemoveDirectoryW( path );
637     
638     r = CreateDirectoryW(path, NULL);
639     ok( r == TRUE, "failed to create directory\n");
640
641     fflags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
642     hdir = CreateFileW(path, GENERIC_READ|SYNCHRONIZE|FILE_LIST_DIRECTORY, 
643                         FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, 
644                         OPEN_EXISTING, fflags, NULL);
645     ok( hdir != INVALID_HANDLE_VALUE, "failed to open directory\n");
646
647     ov.hEvent = CreateEvent( NULL, 1, 0, NULL );
648
649     filter = FILE_NOTIFY_CHANGE_FILE_NAME;
650     filter |= FILE_NOTIFY_CHANGE_DIR_NAME;
651
652     SetLastError(0xd0b00b00);
653     ov.Internal = 0;
654     ov.InternalHigh = 0;
655     memset( buffer, 0, sizeof buffer );
656
657     r = pReadDirectoryChangesW(hdir,NULL,0,FALSE,filter,NULL,&ov,NULL);
658     ok(r==TRUE, "should return true\n");
659
660     r = WaitForSingleObject( ov.hEvent, 0 );
661     ok( r == STATUS_TIMEOUT, "should timeout\n" );
662
663     r = CreateDirectoryW( subdir, NULL );
664     ok( r == TRUE, "failed to create directory\n");
665
666     r = WaitForSingleObject( ov.hEvent, 0 );
667     ok( r == WAIT_OBJECT_0, "event should be ready\n" );
668
669     ok( ov.Internal == STATUS_NOTIFY_ENUM_DIR, "ov.Internal wrong\n");
670     ok( ov.InternalHigh == 0, "ov.InternalHigh wrong\n");
671
672     ov.Internal = 0;
673     ov.InternalHigh = 0;
674     S(U(ov)).Offset = 0;
675     S(U(ov)).OffsetHigh = 0;
676     memset( buffer, 0, sizeof buffer );
677
678     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
679     ok(r==TRUE, "should return true\n");
680
681     r = WaitForSingleObject( ov.hEvent, 0 );
682     ok( r == STATUS_TIMEOUT, "should timeout\n" );
683
684     r = RemoveDirectoryW( subdir );
685     ok( r == TRUE, "failed to remove directory\n");
686
687     r = WaitForSingleObject( ov.hEvent, 1000 );
688     ok( r == WAIT_OBJECT_0, "should be ready\n" );
689
690     ok( ov.Internal == STATUS_NOTIFY_ENUM_DIR, "ov.Internal wrong\n");
691     ok( ov.InternalHigh == 0, "ov.InternalHigh wrong\n");
692
693     pfni = (PFILE_NOTIFY_INFORMATION) buffer;
694     ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
695
696     CloseHandle(hdir);
697
698     r = RemoveDirectoryW( path );
699     ok( r == TRUE, "failed to remove directory\n");
700 }
701
702 static void test_readdirectorychanges_filedir(void)
703 {
704     NTSTATUS r;
705     HANDLE hdir, hfile;
706     char buffer[0x1000];
707     DWORD fflags, filter = 0;
708     OVERLAPPED ov;
709     WCHAR path[MAX_PATH], subdir[MAX_PATH], file[MAX_PATH];
710     static const WCHAR szBoo[] = { '\\','b','o','o',0 };
711     static const WCHAR szHoo[] = { '\\','h','o','o',0 };
712     static const WCHAR szFoo[] = { '\\','f','o','o',0 };
713     PFILE_NOTIFY_INFORMATION pfni;
714
715     SetLastError(0xdeadbeef);
716     r = GetTempPathW( MAX_PATH, path );
717     if (!r && (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED))
718     {
719         win_skip("GetTempPathW is not implemented\n");
720         return;
721     }
722     ok( r != 0, "temp path failed\n");
723     if (!r)
724         return;
725
726     lstrcatW( path, szBoo );
727     lstrcpyW( subdir, path );
728     lstrcatW( subdir, szHoo );
729
730     lstrcpyW( file, path );
731     lstrcatW( file, szFoo );
732
733     DeleteFileW( file );
734     RemoveDirectoryW( subdir );
735     RemoveDirectoryW( path );
736     
737     r = CreateDirectoryW(path, NULL);
738     ok( r == TRUE, "failed to create directory\n");
739
740     fflags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
741     hdir = CreateFileW(path, GENERIC_READ|SYNCHRONIZE|FILE_LIST_DIRECTORY, 
742                         FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, 
743                         OPEN_EXISTING, fflags, NULL);
744     ok( hdir != INVALID_HANDLE_VALUE, "failed to open directory\n");
745
746     ov.hEvent = CreateEvent( NULL, 0, 0, NULL );
747
748     filter = FILE_NOTIFY_CHANGE_FILE_NAME;
749
750     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,TRUE,filter,NULL,&ov,NULL);
751     ok(r==TRUE, "should return true\n");
752
753     r = WaitForSingleObject( ov.hEvent, 10 );
754     ok( r == WAIT_TIMEOUT, "should timeout\n" );
755
756     r = CreateDirectoryW( subdir, NULL );
757     ok( r == TRUE, "failed to create directory\n");
758
759     hfile = CreateFileW( file, GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL );
760     ok( hfile != INVALID_HANDLE_VALUE, "failed to create file\n");
761     ok( CloseHandle(hfile), "failed toc lose file\n");
762
763     r = WaitForSingleObject( ov.hEvent, 1000 );
764     ok( r == WAIT_OBJECT_0, "event should be ready\n" );
765
766     ok( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
767     ok( ov.InternalHigh == 0x12, "ov.InternalHigh wrong\n");
768
769     pfni = (PFILE_NOTIFY_INFORMATION) buffer;
770     ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
771     ok( pfni->Action == FILE_ACTION_ADDED, "action wrong\n" );
772     ok( pfni->FileNameLength == 6, "len wrong\n" );
773     ok( !memcmp(pfni->FileName,&szFoo[1],6), "name wrong\n" );
774
775     r = DeleteFileW( file );
776     ok( r == TRUE, "failed to delete file\n");
777
778     r = RemoveDirectoryW( subdir );
779     ok( r == TRUE, "failed to remove directory\n");
780
781     CloseHandle(hdir);
782
783     r = RemoveDirectoryW( path );
784     ok( r == TRUE, "failed to remove directory\n");
785 }
786
787 static void CALLBACK readdirectorychanges_cr(DWORD error, DWORD len, LPOVERLAPPED ov)
788 {
789     ok(error == 0, "ReadDirectoryChangesW error %d\n", error);
790     ok(ov->hEvent == (void*)0xdeadbeef, "hEvent should not have changed\n");
791 }
792
793 static void test_readdirectorychanges_cr(void)
794 {
795     static const WCHAR szBoo[] = { '\\','b','o','o','\\',0 };
796     static const WCHAR szDir[] = { 'd','i','r',0 };
797     static const WCHAR szFile[] = { 'f','i','l','e',0 };
798
799     WCHAR path[MAX_PATH], file[MAX_PATH], dir[MAX_PATH];
800     FILE_NOTIFY_INFORMATION fni[1024], *fni_next;
801     OVERLAPPED ov;
802     HANDLE hdir, hfile;
803     NTSTATUS r;
804
805     if (!pReadDirectoryChangesW)
806     {
807         win_skip("ReadDirectoryChangesW is not available\n");
808         return;
809     }
810
811     SetLastError(0xdeadbeef);
812     r = GetTempPathW(MAX_PATH, path);
813     if (!r && (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED))
814     {
815         win_skip("GetTempPathW is not implemented\n");
816         return;
817     }
818     ok(r != 0, "temp path failed\n");
819     if (!r)
820         return;
821
822     lstrcatW(path, szBoo);
823     lstrcpyW(dir, path);
824     lstrcatW(dir, szDir);
825     lstrcpyW(file, path);
826     lstrcatW(file, szFile);
827
828     DeleteFileW(file);
829     RemoveDirectoryW(dir);
830     RemoveDirectoryW(path);
831
832     r = CreateDirectoryW(path, NULL);
833     ok(r == TRUE, "failed to create directory\n");
834
835     hdir = CreateFileW(path, GENERIC_READ,
836             FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
837             FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
838     ok(hdir != INVALID_HANDLE_VALUE, "failed to open directory\n");
839
840     memset(&ov, 0, sizeof(ov));
841     ov.hEvent = (void*)0xdeadbeef;
842     r = pReadDirectoryChangesW(hdir, fni, sizeof(fni), FALSE,
843             FILE_NOTIFY_CHANGE_FILE_NAME, NULL, &ov, readdirectorychanges_cr);
844     ok(r == TRUE, "pReadDirectoryChangesW failed\n");
845
846     hfile = CreateFileW(file, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
847     ok(hfile != INVALID_HANDLE_VALUE, "failed to create file\n");
848     CloseHandle(hfile);
849
850     r = SleepEx(1000, TRUE);
851     ok(r != 0, "failed to receive file creation event\n");
852     ok(fni->NextEntryOffset == 0, "there should be no more events in buffer\n");
853     ok(fni->Action == FILE_ACTION_ADDED, "Action = %d\n", fni->Action);
854     ok(fni->FileNameLength == lstrlenW(szFile)*sizeof(WCHAR),
855             "FileNameLength = %d\n", fni->FileNameLength);
856     ok(!memcmp(fni->FileName, szFile, lstrlenW(szFile)*sizeof(WCHAR)),
857             "FileName = %s\n", wine_dbgstr_w(fni->FileName));
858
859     r = pReadDirectoryChangesW(hdir, fni, sizeof(fni), FALSE,
860             FILE_NOTIFY_CHANGE_FILE_NAME, NULL, &ov, readdirectorychanges_cr);
861     ok(r == TRUE, "pReadDirectoryChangesW failed\n");
862
863     /* This event will not be reported */
864     r = CreateDirectoryW(dir, NULL);
865     ok(r == TRUE, "failed to create directory\n");
866
867     r = DeleteFileW(file);
868     ok(r == TRUE, "failed to delete file\n");
869
870     r = SleepEx(1000, TRUE);
871     ok(r != 0, "failed to receive file removal event\n");
872     ok(fni->NextEntryOffset == 0, "there should be no more events in buffer\n");
873     ok(fni->Action == FILE_ACTION_REMOVED, "Action = %d\n", fni->Action);
874     ok(fni->FileNameLength == lstrlenW(szFile)*sizeof(WCHAR),
875             "FileNameLength = %d\n", fni->FileNameLength);
876     ok(!memcmp(fni->FileName, szFile, lstrlenW(szFile)*sizeof(WCHAR)),
877             "FileName = %s\n", wine_dbgstr_w(fni->FileName));
878
879     CloseHandle(hdir);
880
881     hdir = CreateFileW(path, GENERIC_READ,
882             FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
883             FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
884     ok(hdir != INVALID_HANDLE_VALUE, "failed to open directory\n");
885
886     r = pReadDirectoryChangesW(hdir, fni, sizeof(fni), FALSE,
887             FILE_NOTIFY_CHANGE_DIR_NAME, NULL, &ov, readdirectorychanges_cr);
888     ok(r == TRUE, "pReadDirectoryChangesW failed\n");
889
890     r = MoveFileW(dir, file);
891     ok(r == TRUE, "failed to move directory\n");
892
893     r = SleepEx(1000, TRUE);
894     ok(r != 0, "failed to receive directory move event\n");
895     ok(fni->Action == FILE_ACTION_RENAMED_OLD_NAME, "Action = %d\n", fni->Action);
896     ok(fni->FileNameLength == lstrlenW(szDir)*sizeof(WCHAR),
897             "FileNameLength = %d\n", fni->FileNameLength);
898     ok(!memcmp(fni->FileName, szDir, lstrlenW(szDir)*sizeof(WCHAR)),
899             "FileName = %s\n", wine_dbgstr_w(fni->FileName));
900     if (fni->NextEntryOffset)
901         fni_next = (FILE_NOTIFY_INFORMATION*)((char*)fni+fni->NextEntryOffset);
902     else
903     {
904         r = pReadDirectoryChangesW(hdir, fni, sizeof(fni), FALSE,
905                 FILE_NOTIFY_CHANGE_DIR_NAME, NULL, &ov, readdirectorychanges_cr);
906         ok(r == TRUE, "pReadDirectoryChangesW failed\n");
907
908         r = SleepEx(1000, TRUE);
909         ok(r != 0, "failed to receive directory move event\n");
910         fni_next = fni;
911     }
912     ok(fni_next->NextEntryOffset == 0, "there should be no more events in buffer\n");
913     ok(fni_next->Action == FILE_ACTION_RENAMED_NEW_NAME, "Action = %d\n", fni_next->Action);
914     ok(fni_next->FileNameLength == lstrlenW(szFile)*sizeof(WCHAR),
915             "FileNameLength = %d\n", fni_next->FileNameLength);
916     ok(!memcmp(fni_next->FileName, szFile, lstrlenW(szFile)*sizeof(WCHAR)),
917             "FileName = %s\n", wine_dbgstr_w(fni_next->FileName));
918
919     r = CreateDirectoryW(dir, NULL);
920     ok(r == TRUE, "failed to create directory\n");
921
922     r = RemoveDirectoryW(dir);
923     ok(r == TRUE, "failed to remove directory\n");
924
925     r = pReadDirectoryChangesW(hdir, fni, sizeof(fni), FALSE,
926             FILE_NOTIFY_CHANGE_DIR_NAME, NULL, &ov, readdirectorychanges_cr);
927     ok(r == TRUE, "pReadDirectoryChangesW failed\n");
928
929     r = SleepEx(1000, TRUE);
930     ok(r != 0, "failed to receive directory creation event\n");
931     ok(fni->Action == FILE_ACTION_ADDED, "Action = %d\n", fni->Action);
932     ok(fni->FileNameLength == lstrlenW(szDir)*sizeof(WCHAR),
933             "FileNameLength = %d\n", fni->FileNameLength);
934     ok(!memcmp(fni->FileName, szDir, lstrlenW(szDir)*sizeof(WCHAR)),
935             "FileName = %s\n", wine_dbgstr_w(fni->FileName));
936     if (fni->NextEntryOffset)
937         fni_next = (FILE_NOTIFY_INFORMATION*)((char*)fni+fni->NextEntryOffset);
938     else
939     {
940         r = pReadDirectoryChangesW(hdir, fni, sizeof(fni), FALSE,
941                 FILE_NOTIFY_CHANGE_DIR_NAME, NULL, &ov, readdirectorychanges_cr);
942         ok(r == TRUE, "pReadDirectoryChangesW failed\n");
943
944         r = SleepEx(1000, TRUE);
945         ok(r != 0, "failed to receive directory removal event\n");
946         fni_next = fni;
947     }
948     ok(fni_next->NextEntryOffset == 0, "there should be no more events in buffer\n");
949     ok(fni_next->Action == FILE_ACTION_REMOVED, "Action = %d\n", fni_next->Action);
950     ok(fni_next->FileNameLength == lstrlenW(szDir)*sizeof(WCHAR),
951             "FileNameLength = %d\n", fni_next->FileNameLength);
952     ok(!memcmp(fni_next->FileName, szDir, lstrlenW(szDir)*sizeof(WCHAR)),
953             "FileName = %s\n", wine_dbgstr_w(fni_next->FileName));
954
955     CloseHandle(hdir);
956     RemoveDirectoryW(file);
957     RemoveDirectoryW(path);
958 }
959
960 static void test_ffcn_directory_overlap(void)
961 {
962     HANDLE parent_watch, child_watch, parent_thread, child_thread;
963     char workdir[MAX_PATH], parentdir[MAX_PATH], childdir[MAX_PATH];
964     char tempfile[MAX_PATH];
965     DWORD threadId;
966     BOOL ret;
967
968     /* Setup directory hierarchy */
969     ret = GetTempPathA(MAX_PATH, workdir);
970     ok((ret > 0) && (ret <= MAX_PATH),
971        "GetTempPathA error: %d\n", GetLastError());
972
973     ret = GetTempFileNameA(workdir, "fcn", 0, tempfile);
974     ok(ret, "GetTempFileNameA error: %d\n", GetLastError());
975     ret = DeleteFileA(tempfile);
976     ok(ret, "DeleteFileA error: %d\n", GetLastError());
977
978     lstrcpyA(parentdir, tempfile);
979     ret = CreateDirectoryA(parentdir, NULL);
980     ok(ret, "CreateDirectoryA error: %d\n", GetLastError());
981
982     lstrcpyA(childdir, parentdir);
983     lstrcatA(childdir, "\\c");
984     ret = CreateDirectoryA(childdir, NULL);
985     ok(ret, "CreateDirectoryA error: %d\n", GetLastError());
986
987
988     /* When recursively watching overlapping directories, changes in child
989      * should trigger notifications for both child and parent */
990     parent_thread = StartNotificationThread(parentdir, TRUE,
991                                             FILE_NOTIFY_CHANGE_FILE_NAME);
992     child_thread = StartNotificationThread(childdir, TRUE,
993                                             FILE_NOTIFY_CHANGE_FILE_NAME);
994
995     /* Create a file in child */
996     ret = GetTempFileNameA(childdir, "fcn", 0, tempfile);
997     ok(ret, "GetTempFileNameA error: %d\n", GetLastError());
998
999     /* Both watches should trigger */
1000     ret = FinishNotificationThread(parent_thread);
1001     ok(ret, "Missed parent notification\n");
1002     ret = FinishNotificationThread(child_thread);
1003     ok(ret, "Missed child notification\n");
1004
1005     ret = DeleteFileA(tempfile);
1006     ok(ret, "DeleteFileA error: %d\n", GetLastError());
1007
1008
1009     /* Removing a recursive parent watch should not affect child watches. Doing
1010      * so used to crash wineserver. */
1011     parent_watch = FindFirstChangeNotificationA(parentdir, TRUE,
1012                                                 FILE_NOTIFY_CHANGE_FILE_NAME);
1013     ok(parent_watch != INVALID_HANDLE_VALUE,
1014        "FindFirstChangeNotification error: %d\n", GetLastError());
1015     child_watch = FindFirstChangeNotificationA(childdir, TRUE,
1016                                                FILE_NOTIFY_CHANGE_FILE_NAME);
1017     ok(child_watch != INVALID_HANDLE_VALUE,
1018        "FindFirstChangeNotification error: %d\n", GetLastError());
1019
1020     ret = FindCloseChangeNotification(parent_watch);
1021     ok(ret, "FindCloseChangeNotification error: %d\n", GetLastError());
1022
1023     child_thread = CreateThread(NULL, 0, NotificationThread, child_watch, 0,
1024                                 &threadId);
1025     ok(child_thread != NULL, "CreateThread error: %d\n", GetLastError());
1026
1027     /* Create a file in child */
1028     ret = GetTempFileNameA(childdir, "fcn", 0, tempfile);
1029     ok(ret, "GetTempFileNameA error: %d\n", GetLastError());
1030
1031     /* Child watch should trigger */
1032     ret = FinishNotificationThread(child_thread);
1033     ok(ret, "Missed child notification\n");
1034
1035     /* clean up */
1036     ret = DeleteFileA(tempfile);
1037     ok(ret, "DeleteFileA error: %d\n", GetLastError());
1038
1039     ret = RemoveDirectoryA(childdir);
1040     ok(ret, "RemoveDirectoryA error: %d\n", GetLastError());
1041
1042     ret = RemoveDirectoryA(parentdir);
1043     ok(ret, "RemoveDirectoryA error: %d\n", GetLastError());
1044 }
1045
1046 START_TEST(change)
1047 {
1048     HMODULE hkernel32 = GetModuleHandle("kernel32");
1049     pReadDirectoryChangesW = (void *)GetProcAddress(hkernel32, "ReadDirectoryChangesW");
1050
1051     test_ffcnMultipleThreads();
1052     /* The above function runs a test that must occur before FindCloseChangeNotification is run in the
1053        current thread to preserve the emptiness of the wine user APC queue. To ensure this it should be
1054        placed first. */
1055     test_FindFirstChangeNotification();
1056     test_ffcn();
1057     test_readdirectorychanges();
1058     test_readdirectorychanges_null();
1059     test_readdirectorychanges_filedir();
1060     test_readdirectorychanges_cr();
1061     test_ffcn_directory_overlap();
1062 }