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