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