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