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 
NotificationThread(LPVOID arg)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         FindNextChangeNotification(change);
51     }
52 
53     ret = FindCloseChangeNotification(change);
54     ok( ret, "FindCloseChangeNotification error: %d\n",
55        GetLastError());
56 
57     ExitThread((DWORD)notified);
58 }
59 
StartNotificationThread(LPCSTR path,BOOL subtree,DWORD flags)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 
FinishNotificationThread(HANDLE thread)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 
test_FindFirstChangeNotification(void)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 */
test_ffcn(void)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. */
test_ffcnMultipleThreads(void)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 
test_readdirectorychanges(void)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     BOOL got_subdir_change = FALSE;
387 
388     if (!pReadDirectoryChangesW)
389     {
390         win_skip("ReadDirectoryChangesW is not available\n");
391         return;
392     }
393 
394     SetLastError(0xdeadbeef);
395     r = GetTempPathW( MAX_PATH, path );
396     if (!r && (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED))
397     {
398         win_skip("GetTempPathW is not implemented\n");
399         return;
400     }
401     ok( r != 0, "temp path failed\n");
402     if (!r)
403         return;
404 
405     lstrcatW( path, szBoo );
406     lstrcpyW( subdir, path );
407     lstrcatW( subdir, szHoo );
408 
409     lstrcpyW( subsubdir, path );
410     lstrcatW( subsubdir, szGa );
411 
412     RemoveDirectoryW( subsubdir );
413     RemoveDirectoryW( subdir );
414     RemoveDirectoryW( path );
415 
416     r = CreateDirectoryW(path, NULL);
417     ok( r == TRUE, "failed to create directory\n");
418 
419     SetLastError(0xd0b00b00);
420     r = pReadDirectoryChangesW(NULL,NULL,0,FALSE,0,NULL,NULL,NULL);
421     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
422     ok(r==FALSE, "should return false\n");
423 
424     fflags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
425     hdir = CreateFileW(path, GENERIC_READ|SYNCHRONIZE|FILE_LIST_DIRECTORY,
426                         FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
427                         OPEN_EXISTING, fflags, NULL);
428     ok( hdir != INVALID_HANDLE_VALUE, "failed to open directory\n");
429 
430     ov.hEvent = CreateEventW( NULL, 1, 0, NULL );
431 
432     SetLastError(0xd0b00b00);
433     r = pReadDirectoryChangesW(hdir,NULL,0,FALSE,0,NULL,NULL,NULL);
434     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
435     ok(r==FALSE, "should return false\n");
436 
437     SetLastError(0xd0b00b00);
438     r = pReadDirectoryChangesW(hdir,NULL,0,FALSE,0,NULL,&ov,NULL);
439     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
440     ok(r==FALSE, "should return false\n");
441 
442     filter = FILE_NOTIFY_CHANGE_FILE_NAME;
443     filter |= FILE_NOTIFY_CHANGE_DIR_NAME;
444     filter |= FILE_NOTIFY_CHANGE_ATTRIBUTES;
445     filter |= FILE_NOTIFY_CHANGE_SIZE;
446     filter |= FILE_NOTIFY_CHANGE_LAST_WRITE;
447     filter |= FILE_NOTIFY_CHANGE_LAST_ACCESS;
448     filter |= FILE_NOTIFY_CHANGE_CREATION;
449     filter |= FILE_NOTIFY_CHANGE_SECURITY;
450 
451     SetLastError(0xd0b00b00);
452     ov.Internal = 0;
453     ov.InternalHigh = 0;
454     memset( buffer, 0, sizeof buffer );
455 
456     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,-1,NULL,&ov,NULL);
457     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
458     ok(r==FALSE, "should return false\n");
459 
460     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,0,NULL,&ov,NULL);
461     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
462     ok(r==FALSE, "should return false\n");
463 
464     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,TRUE,filter,NULL,&ov,NULL);
465     ok(r==TRUE, "should return true\n");
466 
467     r = WaitForSingleObject( ov.hEvent, 10 );
468     ok( r == STATUS_TIMEOUT, "should timeout\n" );
469 
470     r = CreateDirectoryW( subdir, NULL );
471     ok( r == TRUE, "failed to create directory\n");
472 
473     r = WaitForSingleObject( ov.hEvent, 1000 );
474     ok( r == WAIT_OBJECT_0, "event should be ready\n" );
475 
476     ok( (NTSTATUS)ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
477     ok( ov.InternalHigh == 0x12, "ov.InternalHigh wrong\n");
478 
479     pfni = (PFILE_NOTIFY_INFORMATION) buffer;
480     ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
481     ok( pfni->Action == FILE_ACTION_ADDED, "action wrong\n" );
482     ok( pfni->FileNameLength == 6, "len wrong\n" );
483     ok( !memcmp(pfni->FileName,&szHoo[1],6), "name wrong\n" );
484 
485     ResetEvent(ov.hEvent);
486     SetLastError(0xd0b00b00);
487     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,0,NULL,NULL,NULL);
488     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
489     ok(r==FALSE, "should return false\n");
490 
491     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,0,NULL,&ov,NULL);
492     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
493     ok(r==FALSE, "should return false\n");
494 
495     filter = FILE_NOTIFY_CHANGE_SIZE;
496 
497     SetEvent(ov.hEvent);
498     ov.Internal = 1;
499     ov.InternalHigh = 1;
500     S(U(ov)).Offset = 0;
501     S(U(ov)).OffsetHigh = 0;
502     memset( buffer, 0, sizeof buffer );
503     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
504     ok(r==TRUE, "should return true\n");
505 
506     ok( (NTSTATUS)ov.Internal == STATUS_PENDING, "ov.Internal wrong\n");
507     ok( ov.InternalHigh == 1, "ov.InternalHigh wrong\n");
508 
509     r = WaitForSingleObject( ov.hEvent, 0 );
510     ok( r == STATUS_TIMEOUT, "should timeout\n" );
511 
512     r = RemoveDirectoryW( subdir );
513     ok( r == TRUE, "failed to remove directory\n");
514 
515     r = WaitForSingleObject( ov.hEvent, 1000 );
516     ok( r == WAIT_OBJECT_0, "should be ready\n" );
517 
518     ok( (NTSTATUS)ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
519     ok( ov.InternalHigh == 0x12, "ov.InternalHigh wrong\n");
520 
521     if ((NTSTATUS)ov.Internal == STATUS_SUCCESS)
522     {
523         r = GetOverlappedResult( hdir, &ov, &dwCount, TRUE );
524         ok( r == TRUE, "getoverlappedresult failed\n");
525         ok( dwCount == 0x12, "count wrong\n");
526     }
527 
528     pfni = (PFILE_NOTIFY_INFORMATION) buffer;
529     ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
530     ok( pfni->Action == FILE_ACTION_REMOVED, "action wrong\n" );
531     ok( pfni->FileNameLength == 6, "len wrong\n" );
532     ok( !memcmp(pfni->FileName,&szHoo[1],6), "name wrong\n" );
533 
534     /* what happens if the buffer is too small? */
535     r = pReadDirectoryChangesW(hdir,buffer,0x10,FALSE,filter,NULL,&ov,NULL);
536     ok(r==TRUE, "should return true\n");
537 
538     r = CreateDirectoryW( subdir, NULL );
539     ok( r == TRUE, "failed to create directory\n");
540 
541     r = WaitForSingleObject( ov.hEvent, 1000 );
542     ok( r == WAIT_OBJECT_0, "should be ready\n" );
543 
544     ok( (NTSTATUS)ov.Internal == STATUS_NOTIFY_ENUM_DIR, "ov.Internal wrong\n");
545     ok( ov.InternalHigh == 0, "ov.InternalHigh wrong\n");
546 
547     /* test the recursive watch */
548     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
549     ok(r==TRUE, "should return true\n");
550 
551     r = CreateDirectoryW( subsubdir, NULL );
552     ok( r == TRUE, "failed to create directory\n");
553 
554     while (1)
555     {
556         r = WaitForSingleObject( ov.hEvent, 1000 );
557         ok(r == WAIT_OBJECT_0, "should be ready\n" );
558         if (r == WAIT_TIMEOUT) break;
559 
560         ok((NTSTATUS) ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
561 
562         pfni = (PFILE_NOTIFY_INFORMATION) buffer;
563         while (1)
564         {
565             /* We might get one or more modified events on the parent dir */
566             if (pfni->Action == FILE_ACTION_MODIFIED)
567             {
568                 ok(pfni->FileNameLength == 3 * sizeof(WCHAR), "len wrong\n" );
569                 ok(!memcmp(pfni->FileName, &szGa[1], 3 * sizeof(WCHAR)), "name wrong\n");
570             }
571             else
572             {
573                 ok(pfni->Action == FILE_ACTION_ADDED, "action wrong\n");
574                 ok(pfni->FileNameLength == 6 * sizeof(WCHAR), "len wrong\n" );
575                 ok(!memcmp(pfni->FileName, &szGa[1], 6 * sizeof(WCHAR)), "name wrong\n");
576                 got_subdir_change = TRUE;
577             }
578             if (!pfni->NextEntryOffset) break;
579             pfni = (PFILE_NOTIFY_INFORMATION)((char *)pfni + pfni->NextEntryOffset);
580         }
581 
582         if (got_subdir_change) break;
583 
584         r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
585         ok(r==TRUE, "should return true\n");
586     }
587     ok(got_subdir_change, "didn't get subdir change\n");
588 
589     r = RemoveDirectoryW( subsubdir );
590     ok( r == TRUE, "failed to remove directory\n");
591 
592     ov.Internal = 1;
593     ov.InternalHigh = 1;
594     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
595     ok(r==TRUE, "should return true\n");
596 
597     r = RemoveDirectoryW( subdir );
598     ok( r == TRUE, "failed to remove directory\n");
599 
600     r = WaitForSingleObject( ov.hEvent, 1000 );
601     ok( r == WAIT_OBJECT_0, "should be ready\n" );
602 
603     pfni = (PFILE_NOTIFY_INFORMATION) buffer;
604     /* we may get a notification for the parent dir too */
605     if (pfni->Action == FILE_ACTION_MODIFIED && pfni->NextEntryOffset)
606     {
607         ok( pfni->FileNameLength == 3*sizeof(WCHAR), "len wrong %u\n", pfni->FileNameLength );
608         ok( !memcmp(pfni->FileName,&szGa[1],3*sizeof(WCHAR)), "name wrong\n" );
609         pfni = (PFILE_NOTIFY_INFORMATION)((char *)pfni + pfni->NextEntryOffset);
610     }
611     ok( pfni->NextEntryOffset == 0, "offset wrong %u\n", pfni->NextEntryOffset );
612     ok( pfni->Action == FILE_ACTION_REMOVED, "action wrong %u\n", pfni->Action );
613     ok( pfni->FileNameLength == 6*sizeof(WCHAR), "len wrong %u\n", pfni->FileNameLength );
614     ok( !memcmp(pfni->FileName,&szGa[1],6*sizeof(WCHAR)), "name wrong\n" );
615 
616     ok( (NTSTATUS)ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
617     dwCount = (char *)&pfni->FileName[pfni->FileNameLength/sizeof(WCHAR)] - buffer;
618     ok( ov.InternalHigh == dwCount, "ov.InternalHigh wrong %lu/%u\n",ov.InternalHigh, dwCount );
619 
620     CloseHandle(hdir);
621 
622     r = RemoveDirectoryW( path );
623     ok( r == TRUE, "failed to remove directory\n");
624 }
625 
626 /* show the behaviour when a null buffer is passed */
test_readdirectorychanges_null(void)627 static void test_readdirectorychanges_null(void)
628 {
629     NTSTATUS r;
630     HANDLE hdir;
631     char buffer[0x1000];
632     DWORD fflags, filter = 0;
633     OVERLAPPED ov;
634     WCHAR path[MAX_PATH], subdir[MAX_PATH];
635     static const WCHAR szBoo[] = { '\\','b','o','o',0 };
636     static const WCHAR szHoo[] = { '\\','h','o','o',0 };
637     PFILE_NOTIFY_INFORMATION pfni;
638 
639     if (!pReadDirectoryChangesW)
640     {
641         win_skip("ReadDirectoryChangesW is not available\n");
642         return;
643     }
644     SetLastError(0xdeadbeef);
645     r = GetTempPathW( MAX_PATH, path );
646     if (!r && (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED))
647     {
648         win_skip("GetTempPathW is not implemented\n");
649         return;
650     }
651     ok( r != 0, "temp path failed\n");
652     if (!r)
653         return;
654 
655     lstrcatW( path, szBoo );
656     lstrcpyW( subdir, path );
657     lstrcatW( subdir, szHoo );
658 
659     RemoveDirectoryW( subdir );
660     RemoveDirectoryW( path );
661 
662     r = CreateDirectoryW(path, NULL);
663     ok( r == TRUE, "failed to create directory\n");
664 
665     fflags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
666     hdir = CreateFileW(path, GENERIC_READ|SYNCHRONIZE|FILE_LIST_DIRECTORY,
667                         FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
668                         OPEN_EXISTING, fflags, NULL);
669     ok( hdir != INVALID_HANDLE_VALUE, "failed to open directory\n");
670 
671     ov.hEvent = CreateEventW( NULL, 1, 0, NULL );
672 
673     filter = FILE_NOTIFY_CHANGE_FILE_NAME;
674     filter |= FILE_NOTIFY_CHANGE_DIR_NAME;
675 
676     SetLastError(0xd0b00b00);
677     ov.Internal = 0;
678     ov.InternalHigh = 0;
679     memset( buffer, 0, sizeof buffer );
680 
681     r = pReadDirectoryChangesW(hdir,NULL,0,FALSE,filter,NULL,&ov,NULL);
682     ok(r==TRUE, "should return true\n");
683 
684     r = WaitForSingleObject( ov.hEvent, 0 );
685     ok( r == STATUS_TIMEOUT, "should timeout\n" );
686 
687     r = CreateDirectoryW( subdir, NULL );
688     ok( r == TRUE, "failed to create directory\n");
689 
690     r = WaitForSingleObject( ov.hEvent, 0 );
691     ok( r == WAIT_OBJECT_0, "event should be ready\n" );
692 
693     ok( (NTSTATUS)ov.Internal == STATUS_NOTIFY_ENUM_DIR, "ov.Internal wrong\n");
694     ok( ov.InternalHigh == 0, "ov.InternalHigh wrong\n");
695 
696     ov.Internal = 0;
697     ov.InternalHigh = 0;
698     S(U(ov)).Offset = 0;
699     S(U(ov)).OffsetHigh = 0;
700     memset( buffer, 0, sizeof buffer );
701 
702     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
703     ok(r==TRUE, "should return true\n");
704 
705     r = WaitForSingleObject( ov.hEvent, 0 );
706     ok( r == STATUS_TIMEOUT, "should timeout\n" );
707 
708     r = RemoveDirectoryW( subdir );
709     ok( r == TRUE, "failed to remove directory\n");
710 
711     r = WaitForSingleObject( ov.hEvent, 1000 );
712     ok( r == WAIT_OBJECT_0, "should be ready\n" );
713 
714     ok( (NTSTATUS)ov.Internal == STATUS_NOTIFY_ENUM_DIR, "ov.Internal wrong\n");
715     ok( ov.InternalHigh == 0, "ov.InternalHigh wrong\n");
716 
717     pfni = (PFILE_NOTIFY_INFORMATION) buffer;
718     ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
719 
720     CloseHandle(hdir);
721 
722     r = RemoveDirectoryW( path );
723     ok( r == TRUE, "failed to remove directory\n");
724 }
725 
test_readdirectorychanges_filedir(void)726 static void test_readdirectorychanges_filedir(void)
727 {
728     NTSTATUS r;
729     HANDLE hdir, hfile;
730     char buffer[0x1000];
731     DWORD fflags, filter = 0;
732     OVERLAPPED ov;
733     WCHAR path[MAX_PATH], subdir[MAX_PATH], file[MAX_PATH];
734     static const WCHAR szBoo[] = { '\\','b','o','o',0 };
735     static const WCHAR szHoo[] = { '\\','h','o','o',0 };
736     static const WCHAR szFoo[] = { '\\','f','o','o',0 };
737     PFILE_NOTIFY_INFORMATION pfni;
738 
739     SetLastError(0xdeadbeef);
740     r = GetTempPathW( MAX_PATH, path );
741     if (!r && (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED))
742     {
743         win_skip("GetTempPathW is not implemented\n");
744         return;
745     }
746     ok( r != 0, "temp path failed\n");
747     if (!r)
748         return;
749 
750     lstrcatW( path, szBoo );
751     lstrcpyW( subdir, path );
752     lstrcatW( subdir, szHoo );
753 
754     lstrcpyW( file, path );
755     lstrcatW( file, szFoo );
756 
757     DeleteFileW( file );
758     RemoveDirectoryW( subdir );
759     RemoveDirectoryW( path );
760 
761     r = CreateDirectoryW(path, NULL);
762     ok( r == TRUE, "failed to create directory\n");
763 
764     fflags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
765     hdir = CreateFileW(path, GENERIC_READ|SYNCHRONIZE|FILE_LIST_DIRECTORY,
766                         FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
767                         OPEN_EXISTING, fflags, NULL);
768     ok( hdir != INVALID_HANDLE_VALUE, "failed to open directory\n");
769 
770     ov.hEvent = CreateEventW( NULL, 0, 0, NULL );
771 
772     filter = FILE_NOTIFY_CHANGE_FILE_NAME;
773 
774     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,TRUE,filter,NULL,&ov,NULL);
775     ok(r==TRUE, "should return true\n");
776 
777     r = WaitForSingleObject( ov.hEvent, 10 );
778     ok( r == WAIT_TIMEOUT, "should timeout\n" );
779 
780     r = CreateDirectoryW( subdir, NULL );
781     ok( r == TRUE, "failed to create directory\n");
782 
783     hfile = CreateFileW( file, GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL );
784     ok( hfile != INVALID_HANDLE_VALUE, "failed to create file\n");
785     ok( CloseHandle(hfile), "failed to close file\n");
786 
787     r = WaitForSingleObject( ov.hEvent, 1000 );
788     ok( r == WAIT_OBJECT_0, "event should be ready\n" );
789 
790     ok( (NTSTATUS)ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
791     ok( ov.InternalHigh == 0x12, "ov.InternalHigh wrong\n");
792 
793     pfni = (PFILE_NOTIFY_INFORMATION) buffer;
794     ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
795     ok( pfni->Action == FILE_ACTION_ADDED, "action wrong\n" );
796     ok( pfni->FileNameLength == 6, "len wrong\n" );
797     ok( !memcmp(pfni->FileName,&szFoo[1],6), "name wrong\n" );
798 
799     r = DeleteFileW( file );
800     ok( r == TRUE, "failed to delete file\n");
801 
802     r = RemoveDirectoryW( subdir );
803     ok( r == TRUE, "failed to remove directory\n");
804 
805     CloseHandle(hdir);
806 
807     r = RemoveDirectoryW( path );
808     ok( r == TRUE, "failed to remove directory\n");
809 }
810 
readdirectorychanges_cr(DWORD error,DWORD len,LPOVERLAPPED ov)811 static void CALLBACK readdirectorychanges_cr(DWORD error, DWORD len, LPOVERLAPPED ov)
812 {
813     ok(error == 0, "ReadDirectoryChangesW error %d\n", error);
814     ok(ov->hEvent == (void*)0xdeadbeef, "hEvent should not have changed\n");
815 }
816 
test_readdirectorychanges_cr(void)817 static void test_readdirectorychanges_cr(void)
818 {
819     static const WCHAR szBoo[] = { '\\','b','o','o','\\',0 };
820     static const WCHAR szDir[] = { 'd','i','r',0 };
821     static const WCHAR szFile[] = { 'f','i','l','e',0 };
822     static const WCHAR szBackslash[] = { '\\',0 };
823 
824     WCHAR path[MAX_PATH], file[MAX_PATH], dir[MAX_PATH], sub_file[MAX_PATH];
825     FILE_NOTIFY_INFORMATION fni[1024], *fni_next;
826     OVERLAPPED ov;
827     HANDLE hdir, hfile;
828     NTSTATUS r;
829 
830     if (!pReadDirectoryChangesW)
831     {
832         win_skip("ReadDirectoryChangesW is not available\n");
833         return;
834     }
835 
836     SetLastError(0xdeadbeef);
837     r = GetTempPathW(MAX_PATH, path);
838     if (!r && (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED))
839     {
840         win_skip("GetTempPathW is not implemented\n");
841         return;
842     }
843     ok(r != 0, "temp path failed\n");
844     if (!r)
845         return;
846 
847     lstrcatW(path, szBoo);
848     lstrcpyW(dir, path);
849     lstrcatW(dir, szDir);
850     lstrcpyW(file, path);
851     lstrcatW(file, szFile);
852     lstrcpyW(sub_file, dir);
853     lstrcatW(sub_file, szBackslash);
854     lstrcatW(sub_file, szFile);
855 
856     DeleteFileW(file);
857     RemoveDirectoryW(dir);
858     RemoveDirectoryW(path);
859 
860     r = CreateDirectoryW(path, NULL);
861     ok(r == TRUE, "failed to create directory\n");
862 
863     hdir = CreateFileW(path, GENERIC_READ,
864             FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
865             FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
866     ok(hdir != INVALID_HANDLE_VALUE, "failed to open directory\n");
867 
868     memset(&ov, 0, sizeof(ov));
869     ov.hEvent = (void*)0xdeadbeef;
870     r = pReadDirectoryChangesW(hdir, fni, sizeof(fni), FALSE,
871             FILE_NOTIFY_CHANGE_FILE_NAME, NULL, &ov, readdirectorychanges_cr);
872     ok(r == TRUE, "pReadDirectoryChangesW failed\n");
873 
874     hfile = CreateFileW(file, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
875     ok(hfile != INVALID_HANDLE_VALUE, "failed to create file\n");
876     CloseHandle(hfile);
877 
878     r = SleepEx(1000, TRUE);
879     ok(r != 0, "failed to receive file creation event\n");
880     ok(fni->NextEntryOffset == 0, "there should be no more events in buffer\n");
881     ok(fni->Action == FILE_ACTION_ADDED, "Action = %d\n", fni->Action);
882     ok(fni->FileNameLength == lstrlenW(szFile)*sizeof(WCHAR),
883             "FileNameLength = %d\n", fni->FileNameLength);
884     ok(!memcmp(fni->FileName, szFile, lstrlenW(szFile)*sizeof(WCHAR)),
885             "FileName = %s\n", wine_dbgstr_wn(fni->FileName, fni->FileNameLength/sizeof(WCHAR)));
886 
887     r = pReadDirectoryChangesW(hdir, fni, sizeof(fni), FALSE,
888             FILE_NOTIFY_CHANGE_FILE_NAME, NULL, &ov, readdirectorychanges_cr);
889     ok(r == TRUE, "pReadDirectoryChangesW failed\n");
890 
891     /* This event will not be reported */
892     r = CreateDirectoryW(dir, NULL);
893     ok(r == TRUE, "failed to create directory\n");
894 
895     r = MoveFileW(file, sub_file);
896     ok(r == TRUE, "failed to move file\n");
897 
898     r = SleepEx(1000, TRUE);
899     ok(r != 0, "failed to receive file move event\n");
900     ok(fni->NextEntryOffset == 0, "there should be no more events in buffer\n");
901     ok(fni->Action == FILE_ACTION_REMOVED, "Action = %d\n", fni->Action);
902     ok(fni->FileNameLength == lstrlenW(szFile)*sizeof(WCHAR),
903             "FileNameLength = %d\n", fni->FileNameLength);
904     ok(!memcmp(fni->FileName, szFile, lstrlenW(szFile)*sizeof(WCHAR)),
905             "FileName = %s\n", wine_dbgstr_wn(fni->FileName, fni->FileNameLength/sizeof(WCHAR)));
906 
907     r = pReadDirectoryChangesW(hdir, fni, sizeof(fni), FALSE,
908             FILE_NOTIFY_CHANGE_FILE_NAME, NULL, &ov, readdirectorychanges_cr);
909     ok(r == TRUE, "pReadDirectoryChangesW failed\n");
910 
911     r = MoveFileW(sub_file, file);
912     ok(r == TRUE, "failed to move file\n");
913 
914     r = SleepEx(1000, TRUE);
915     ok(r != 0, "failed to receive file move event\n");
916     ok(fni->NextEntryOffset == 0, "there should be no more events in buffer\n");
917     ok(fni->Action == FILE_ACTION_ADDED, "Action = %d\n", fni->Action);
918     ok(fni->FileNameLength == lstrlenW(szFile)*sizeof(WCHAR),
919             "FileNameLength = %d\n", fni->FileNameLength);
920     ok(!memcmp(fni->FileName, szFile, lstrlenW(szFile)*sizeof(WCHAR)),
921             "FileName = %s\n", wine_dbgstr_wn(fni->FileName, fni->FileNameLength/sizeof(WCHAR)));
922 
923     r = pReadDirectoryChangesW(hdir, fni, sizeof(fni), FALSE,
924             FILE_NOTIFY_CHANGE_FILE_NAME, NULL, &ov, readdirectorychanges_cr);
925     ok(r == TRUE, "pReadDirectoryChangesW failed\n");
926 
927     r = DeleteFileW(file);
928     ok(r == TRUE, "failed to delete file\n");
929 
930     r = SleepEx(1000, TRUE);
931     ok(r != 0, "failed to receive file removal event\n");
932     ok(fni->NextEntryOffset == 0, "there should be no more events in buffer\n");
933     ok(fni->Action == FILE_ACTION_REMOVED, "Action = %d\n", fni->Action);
934     ok(fni->FileNameLength == lstrlenW(szFile)*sizeof(WCHAR),
935             "FileNameLength = %d\n", fni->FileNameLength);
936     ok(!memcmp(fni->FileName, szFile, lstrlenW(szFile)*sizeof(WCHAR)),
937             "FileName = %s\n", wine_dbgstr_wn(fni->FileName, fni->FileNameLength/sizeof(WCHAR)));
938 
939     CloseHandle(hdir);
940 
941     hdir = CreateFileW(path, GENERIC_READ,
942             FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
943             FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
944     ok(hdir != INVALID_HANDLE_VALUE, "failed to open directory\n");
945 
946     r = pReadDirectoryChangesW(hdir, fni, sizeof(fni), FALSE,
947             FILE_NOTIFY_CHANGE_DIR_NAME, NULL, &ov, readdirectorychanges_cr);
948     ok(r == TRUE, "pReadDirectoryChangesW failed\n");
949 
950     r = MoveFileW(dir, file);
951     ok(r == TRUE, "failed to move directory\n");
952 
953     r = SleepEx(1000, TRUE);
954     ok(r != 0, "failed to receive directory move event\n");
955     if (fni->Action == FILE_ACTION_RENAMED_OLD_NAME)
956     {
957         ok(fni->Action == FILE_ACTION_RENAMED_OLD_NAME, "Action = %d\n", fni->Action);
958         ok(fni->FileNameLength == lstrlenW(szDir)*sizeof(WCHAR),
959                 "FileNameLength = %d\n", fni->FileNameLength);
960         ok(!memcmp(fni->FileName, szDir, lstrlenW(szDir)*sizeof(WCHAR)),
961                 "FileName = %s\n", wine_dbgstr_wn(fni->FileName, fni->FileNameLength/sizeof(WCHAR)));
962         ok(fni->NextEntryOffset != 0, "no next entry in movement event\n");
963         fni_next = (FILE_NOTIFY_INFORMATION*)((char*)fni+fni->NextEntryOffset);
964         ok(fni_next->NextEntryOffset == 0, "there should be no more events in buffer\n");
965         ok(fni_next->Action == FILE_ACTION_RENAMED_NEW_NAME, "Action = %d\n", fni_next->Action);
966         ok(fni_next->FileNameLength == lstrlenW(szFile)*sizeof(WCHAR),
967                 "FileNameLength = %d\n", fni_next->FileNameLength);
968         ok(!memcmp(fni_next->FileName, szFile, lstrlenW(szFile)*sizeof(WCHAR)),
969                 "FileName = %s\n", wine_dbgstr_wn(fni_next->FileName, fni_next->FileNameLength/sizeof(WCHAR)));
970     }
971     else
972     {
973         todo_wine ok(0, "Expected rename event\n");
974 
975         if (fni->NextEntryOffset == 0)
976         {
977             r = pReadDirectoryChangesW(hdir, fni, sizeof(fni), FALSE,
978                     FILE_NOTIFY_CHANGE_DIR_NAME, NULL, &ov, readdirectorychanges_cr);
979             ok(r == TRUE, "pReadDirectoryChangesW failed\n");
980 
981             r = SleepEx(1000, TRUE);
982             ok(r != 0, "failed to receive directory move event\n");
983         }
984     }
985 
986     r = CreateDirectoryW(dir, NULL);
987     ok(r == TRUE, "failed to create directory\n");
988 
989     r = RemoveDirectoryW(dir);
990     ok(r == TRUE, "failed to remove directory\n");
991 
992     r = pReadDirectoryChangesW(hdir, fni, sizeof(fni), FALSE,
993             FILE_NOTIFY_CHANGE_DIR_NAME, NULL, &ov, readdirectorychanges_cr);
994     ok(r == TRUE, "pReadDirectoryChangesW failed\n");
995 
996     r = SleepEx(1000, TRUE);
997     ok(r != 0, "failed to receive directory creation event\n");
998     ok(fni->Action == FILE_ACTION_ADDED, "Action = %d\n", fni->Action);
999     ok(fni->FileNameLength == lstrlenW(szDir)*sizeof(WCHAR),
1000             "FileNameLength = %d\n", fni->FileNameLength);
1001     ok(!memcmp(fni->FileName, szDir, lstrlenW(szDir)*sizeof(WCHAR)),
1002             "FileName = %s\n", wine_dbgstr_wn(fni->FileName, fni->FileNameLength/sizeof(WCHAR)));
1003     if (fni->NextEntryOffset)
1004         fni_next = (FILE_NOTIFY_INFORMATION*)((char*)fni+fni->NextEntryOffset);
1005     else
1006     {
1007         r = pReadDirectoryChangesW(hdir, fni, sizeof(fni), FALSE,
1008                 FILE_NOTIFY_CHANGE_DIR_NAME, NULL, &ov, readdirectorychanges_cr);
1009         ok(r == TRUE, "pReadDirectoryChangesW failed\n");
1010 
1011         r = SleepEx(1000, TRUE);
1012         ok(r != 0, "failed to receive directory removal event\n");
1013         fni_next = fni;
1014     }
1015     ok(fni_next->NextEntryOffset == 0, "there should be no more events in buffer\n");
1016     ok(fni_next->Action == FILE_ACTION_REMOVED, "Action = %d\n", fni_next->Action);
1017     ok(fni_next->FileNameLength == lstrlenW(szDir)*sizeof(WCHAR),
1018             "FileNameLength = %d\n", fni_next->FileNameLength);
1019     ok(!memcmp(fni_next->FileName, szDir, lstrlenW(szDir)*sizeof(WCHAR)),
1020             "FileName = %s\n", wine_dbgstr_wn(fni_next->FileName, fni_next->FileNameLength/sizeof(WCHAR)));
1021 
1022     CloseHandle(hdir);
1023     RemoveDirectoryW(file);
1024     RemoveDirectoryW(path);
1025 }
1026 
test_ffcn_directory_overlap(void)1027 static void test_ffcn_directory_overlap(void)
1028 {
1029     HANDLE parent_watch, child_watch, parent_thread, child_thread;
1030     char workdir[MAX_PATH], parentdir[MAX_PATH], childdir[MAX_PATH];
1031     char tempfile[MAX_PATH];
1032     DWORD threadId;
1033     BOOL ret;
1034 
1035     /* Setup directory hierarchy */
1036     ret = GetTempPathA(MAX_PATH, workdir);
1037     ok((ret > 0) && (ret <= MAX_PATH),
1038        "GetTempPathA error: %d\n", GetLastError());
1039 
1040     ret = GetTempFileNameA(workdir, "fcn", 0, tempfile);
1041     ok(ret, "GetTempFileNameA error: %d\n", GetLastError());
1042     ret = DeleteFileA(tempfile);
1043     ok(ret, "DeleteFileA error: %d\n", GetLastError());
1044 
1045     lstrcpyA(parentdir, tempfile);
1046     ret = CreateDirectoryA(parentdir, NULL);
1047     ok(ret, "CreateDirectoryA error: %d\n", GetLastError());
1048 
1049     lstrcpyA(childdir, parentdir);
1050     lstrcatA(childdir, "\\c");
1051     ret = CreateDirectoryA(childdir, NULL);
1052     ok(ret, "CreateDirectoryA error: %d\n", GetLastError());
1053 
1054 
1055     /* When recursively watching overlapping directories, changes in child
1056      * should trigger notifications for both child and parent */
1057     parent_thread = StartNotificationThread(parentdir, TRUE,
1058                                             FILE_NOTIFY_CHANGE_FILE_NAME);
1059     child_thread = StartNotificationThread(childdir, TRUE,
1060                                             FILE_NOTIFY_CHANGE_FILE_NAME);
1061 
1062     /* Create a file in child */
1063     ret = GetTempFileNameA(childdir, "fcn", 0, tempfile);
1064     ok(ret, "GetTempFileNameA error: %d\n", GetLastError());
1065 
1066     /* Both watches should trigger */
1067     ret = FinishNotificationThread(parent_thread);
1068     ok(ret, "Missed parent notification\n");
1069     ret = FinishNotificationThread(child_thread);
1070     ok(ret, "Missed child notification\n");
1071 
1072     ret = DeleteFileA(tempfile);
1073     ok(ret, "DeleteFileA error: %d\n", GetLastError());
1074 
1075 
1076     /* Removing a recursive parent watch should not affect child watches. Doing
1077      * so used to crash wineserver. */
1078     parent_watch = FindFirstChangeNotificationA(parentdir, TRUE,
1079                                                 FILE_NOTIFY_CHANGE_FILE_NAME);
1080     ok(parent_watch != INVALID_HANDLE_VALUE,
1081        "FindFirstChangeNotification error: %d\n", GetLastError());
1082     child_watch = FindFirstChangeNotificationA(childdir, TRUE,
1083                                                FILE_NOTIFY_CHANGE_FILE_NAME);
1084     ok(child_watch != INVALID_HANDLE_VALUE,
1085        "FindFirstChangeNotification error: %d\n", GetLastError());
1086 
1087     ret = FindCloseChangeNotification(parent_watch);
1088     ok(ret, "FindCloseChangeNotification error: %d\n", GetLastError());
1089 
1090     child_thread = CreateThread(NULL, 0, NotificationThread, child_watch, 0,
1091                                 &threadId);
1092     ok(child_thread != NULL, "CreateThread error: %d\n", GetLastError());
1093 
1094     /* Create a file in child */
1095     ret = GetTempFileNameA(childdir, "fcn", 0, tempfile);
1096     ok(ret, "GetTempFileNameA error: %d\n", GetLastError());
1097 
1098     /* Child watch should trigger */
1099     ret = FinishNotificationThread(child_thread);
1100     ok(ret, "Missed child notification\n");
1101 
1102     /* clean up */
1103     ret = DeleteFileA(tempfile);
1104     ok(ret, "DeleteFileA error: %d\n", GetLastError());
1105 
1106     ret = RemoveDirectoryA(childdir);
1107     ok(ret, "RemoveDirectoryA error: %d\n", GetLastError());
1108 
1109     ret = RemoveDirectoryA(parentdir);
1110     ok(ret, "RemoveDirectoryA error: %d\n", GetLastError());
1111 }
1112 
START_TEST(change)1113 START_TEST(change)
1114 {
1115     HMODULE hkernel32 = GetModuleHandleA("kernel32.dll");
1116     pReadDirectoryChangesW = (void *)GetProcAddress(hkernel32, "ReadDirectoryChangesW");
1117 
1118     test_ffcnMultipleThreads();
1119     /* The above function runs a test that must occur before FindCloseChangeNotification is run in the
1120        current thread to preserve the emptiness of the wine user APC queue. To ensure this it should be
1121        placed first. */
1122     test_FindFirstChangeNotification();
1123     test_ffcn();
1124     test_readdirectorychanges();
1125     test_readdirectorychanges_null();
1126     test_readdirectorychanges_filedir();
1127     test_readdirectorychanges_cr();
1128     test_ffcn_directory_overlap();
1129 }
1130