kernel32/tests: Fix a thread handle leak.
[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 typedef BOOL (WINAPI *fnReadDirectoryChangesW)(HANDLE,LPVOID,DWORD,BOOL,DWORD,
373                          LPDWORD,LPOVERLAPPED,LPOVERLAPPED_COMPLETION_ROUTINE);
374 fnReadDirectoryChangesW pReadDirectoryChangesW;
375
376 static void test_readdirectorychanges(void)
377 {
378     HANDLE hdir;
379     char buffer[0x1000];
380     DWORD fflags, filter = 0, r, dwCount;
381     OVERLAPPED ov;
382     WCHAR path[MAX_PATH], subdir[MAX_PATH], subsubdir[MAX_PATH];
383     static const WCHAR szBoo[] = { '\\','b','o','o',0 };
384     static const WCHAR szHoo[] = { '\\','h','o','o',0 };
385     static const WCHAR szGa[] = { '\\','h','o','o','\\','g','a',0 };
386     PFILE_NOTIFY_INFORMATION pfni;
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 = CreateEvent( 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( 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( 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( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
519     ok( ov.InternalHigh == 0x12, "ov.InternalHigh wrong\n");
520
521     if (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( 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     r = WaitForSingleObject( ov.hEvent, 1000 );
555     ok( r == WAIT_OBJECT_0, "should be ready\n" );
556
557     ok( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
558     ok( ov.InternalHigh == 0x18, "ov.InternalHigh wrong\n");
559
560     pfni = (PFILE_NOTIFY_INFORMATION) buffer;
561     ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
562     ok( pfni->Action == FILE_ACTION_ADDED, "action wrong\n" );
563     ok( pfni->FileNameLength == 6*sizeof(WCHAR), "len wrong\n" );
564     ok( !memcmp(pfni->FileName,&szGa[1],6*sizeof(WCHAR)), "name wrong\n" );
565
566     r = RemoveDirectoryW( subsubdir );
567     ok( r == TRUE, "failed to remove directory\n");
568
569     ov.Internal = 1;
570     ov.InternalHigh = 1;
571     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
572     ok(r==TRUE, "should return true\n");
573
574     r = RemoveDirectoryW( subdir );
575     ok( r == TRUE, "failed to remove directory\n");
576
577     r = WaitForSingleObject( ov.hEvent, 1000 );
578     ok( r == WAIT_OBJECT_0, "should be ready\n" );
579
580     pfni = (PFILE_NOTIFY_INFORMATION) buffer;
581     /* we may get a notification for the parent dir too */
582     if (pfni->Action == FILE_ACTION_MODIFIED && pfni->NextEntryOffset)
583     {
584         ok( pfni->FileNameLength == 3*sizeof(WCHAR), "len wrong %u\n", pfni->FileNameLength );
585         ok( !memcmp(pfni->FileName,&szGa[1],3*sizeof(WCHAR)), "name wrong\n" );
586         pfni = (PFILE_NOTIFY_INFORMATION)((char *)pfni + pfni->NextEntryOffset);
587     }
588     ok( pfni->NextEntryOffset == 0, "offset wrong %u\n", pfni->NextEntryOffset );
589     ok( pfni->Action == FILE_ACTION_REMOVED, "action wrong %u\n", pfni->Action );
590     ok( pfni->FileNameLength == 6*sizeof(WCHAR), "len wrong %u\n", pfni->FileNameLength );
591     ok( !memcmp(pfni->FileName,&szGa[1],6*sizeof(WCHAR)), "name wrong\n" );
592
593     ok( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
594     dwCount = (char *)&pfni->FileName[pfni->FileNameLength/sizeof(WCHAR)] - buffer;
595     ok( ov.InternalHigh == dwCount, "ov.InternalHigh wrong %lu/%u\n",ov.InternalHigh, dwCount );
596
597     CloseHandle(hdir);
598
599     r = RemoveDirectoryW( path );
600     ok( r == TRUE, "failed to remove directory\n");
601 }
602
603 /* show the behaviour when a null buffer is passed */
604 static void test_readdirectorychanges_null(void)
605 {
606     NTSTATUS r;
607     HANDLE hdir;
608     char buffer[0x1000];
609     DWORD fflags, filter = 0;
610     OVERLAPPED ov;
611     WCHAR path[MAX_PATH], subdir[MAX_PATH];
612     static const WCHAR szBoo[] = { '\\','b','o','o',0 };
613     static const WCHAR szHoo[] = { '\\','h','o','o',0 };
614     PFILE_NOTIFY_INFORMATION pfni;
615
616     if (!pReadDirectoryChangesW)
617     {
618         win_skip("ReadDirectoryChangesW is not available\n");
619         return;
620     }
621     SetLastError(0xdeadbeef);
622     r = GetTempPathW( MAX_PATH, path );
623     if (!r && (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED))
624     {
625         win_skip("GetTempPathW is not implemented\n");
626         return;
627     }
628     ok( r != 0, "temp path failed\n");
629     if (!r)
630         return;
631
632     lstrcatW( path, szBoo );
633     lstrcpyW( subdir, path );
634     lstrcatW( subdir, szHoo );
635
636     RemoveDirectoryW( subdir );
637     RemoveDirectoryW( path );
638     
639     r = CreateDirectoryW(path, NULL);
640     ok( r == TRUE, "failed to create directory\n");
641
642     fflags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
643     hdir = CreateFileW(path, GENERIC_READ|SYNCHRONIZE|FILE_LIST_DIRECTORY, 
644                         FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, 
645                         OPEN_EXISTING, fflags, NULL);
646     ok( hdir != INVALID_HANDLE_VALUE, "failed to open directory\n");
647
648     ov.hEvent = CreateEvent( NULL, 1, 0, NULL );
649
650     filter = FILE_NOTIFY_CHANGE_FILE_NAME;
651     filter |= FILE_NOTIFY_CHANGE_DIR_NAME;
652
653     SetLastError(0xd0b00b00);
654     ov.Internal = 0;
655     ov.InternalHigh = 0;
656     memset( buffer, 0, sizeof buffer );
657
658     r = pReadDirectoryChangesW(hdir,NULL,0,FALSE,filter,NULL,&ov,NULL);
659     ok(r==TRUE, "should return true\n");
660
661     r = WaitForSingleObject( ov.hEvent, 0 );
662     ok( r == STATUS_TIMEOUT, "should timeout\n" );
663
664     r = CreateDirectoryW( subdir, NULL );
665     ok( r == TRUE, "failed to create directory\n");
666
667     r = WaitForSingleObject( ov.hEvent, 0 );
668     ok( r == WAIT_OBJECT_0, "event should be ready\n" );
669
670     ok( ov.Internal == STATUS_NOTIFY_ENUM_DIR, "ov.Internal wrong\n");
671     ok( ov.InternalHigh == 0, "ov.InternalHigh wrong\n");
672
673     ov.Internal = 0;
674     ov.InternalHigh = 0;
675     S(U(ov)).Offset = 0;
676     S(U(ov)).OffsetHigh = 0;
677     memset( buffer, 0, sizeof buffer );
678
679     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
680     ok(r==TRUE, "should return true\n");
681
682     r = WaitForSingleObject( ov.hEvent, 0 );
683     ok( r == STATUS_TIMEOUT, "should timeout\n" );
684
685     r = RemoveDirectoryW( subdir );
686     ok( r == TRUE, "failed to remove directory\n");
687
688     r = WaitForSingleObject( ov.hEvent, 1000 );
689     ok( r == WAIT_OBJECT_0, "should be ready\n" );
690
691     ok( ov.Internal == STATUS_NOTIFY_ENUM_DIR, "ov.Internal wrong\n");
692     ok( ov.InternalHigh == 0, "ov.InternalHigh wrong\n");
693
694     pfni = (PFILE_NOTIFY_INFORMATION) buffer;
695     ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
696
697     CloseHandle(hdir);
698
699     r = RemoveDirectoryW( path );
700     ok( r == TRUE, "failed to remove directory\n");
701 }
702
703 static void test_readdirectorychanges_filedir(void)
704 {
705     NTSTATUS r;
706     HANDLE hdir, hfile;
707     char buffer[0x1000];
708     DWORD fflags, filter = 0;
709     OVERLAPPED ov;
710     WCHAR path[MAX_PATH], subdir[MAX_PATH], file[MAX_PATH];
711     static const WCHAR szBoo[] = { '\\','b','o','o',0 };
712     static const WCHAR szHoo[] = { '\\','h','o','o',0 };
713     static const WCHAR szFoo[] = { '\\','f','o','o',0 };
714     PFILE_NOTIFY_INFORMATION pfni;
715
716     SetLastError(0xdeadbeef);
717     r = GetTempPathW( MAX_PATH, path );
718     if (!r && (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED))
719     {
720         win_skip("GetTempPathW is not implemented\n");
721         return;
722     }
723     ok( r != 0, "temp path failed\n");
724     if (!r)
725         return;
726
727     lstrcatW( path, szBoo );
728     lstrcpyW( subdir, path );
729     lstrcatW( subdir, szHoo );
730
731     lstrcpyW( file, path );
732     lstrcatW( file, szFoo );
733
734     DeleteFileW( file );
735     RemoveDirectoryW( subdir );
736     RemoveDirectoryW( path );
737     
738     r = CreateDirectoryW(path, NULL);
739     ok( r == TRUE, "failed to create directory\n");
740
741     fflags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
742     hdir = CreateFileW(path, GENERIC_READ|SYNCHRONIZE|FILE_LIST_DIRECTORY, 
743                         FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, 
744                         OPEN_EXISTING, fflags, NULL);
745     ok( hdir != INVALID_HANDLE_VALUE, "failed to open directory\n");
746
747     ov.hEvent = CreateEvent( NULL, 0, 0, NULL );
748
749     filter = FILE_NOTIFY_CHANGE_FILE_NAME;
750
751     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,TRUE,filter,NULL,&ov,NULL);
752     ok(r==TRUE, "should return true\n");
753
754     r = WaitForSingleObject( ov.hEvent, 10 );
755     ok( r == WAIT_TIMEOUT, "should timeout\n" );
756
757     r = CreateDirectoryW( subdir, NULL );
758     ok( r == TRUE, "failed to create directory\n");
759
760     hfile = CreateFileW( file, GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL );
761     ok( hfile != INVALID_HANDLE_VALUE, "failed to create file\n");
762     ok( CloseHandle(hfile), "failed toc lose file\n");
763
764     r = WaitForSingleObject( ov.hEvent, 1000 );
765     ok( r == WAIT_OBJECT_0, "event should be ready\n" );
766
767     ok( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
768     ok( ov.InternalHigh == 0x12, "ov.InternalHigh wrong\n");
769
770     pfni = (PFILE_NOTIFY_INFORMATION) buffer;
771     ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
772     ok( pfni->Action == FILE_ACTION_ADDED, "action wrong\n" );
773     ok( pfni->FileNameLength == 6, "len wrong\n" );
774     ok( !memcmp(pfni->FileName,&szFoo[1],6), "name wrong\n" );
775
776     r = DeleteFileW( file );
777     ok( r == TRUE, "failed to delete file\n");
778
779     r = RemoveDirectoryW( subdir );
780     ok( r == TRUE, "failed to remove directory\n");
781
782     CloseHandle(hdir);
783
784     r = RemoveDirectoryW( path );
785     ok( r == TRUE, "failed to remove directory\n");
786 }
787
788 static void test_ffcn_directory_overlap(void)
789 {
790     HANDLE parent_watch, child_watch, parent_thread, child_thread;
791     char workdir[MAX_PATH], parentdir[MAX_PATH], childdir[MAX_PATH];
792     char tempfile[MAX_PATH];
793     DWORD threadId;
794     BOOL ret;
795
796     /* Setup directory hierarchy */
797     ret = GetTempPathA(MAX_PATH, workdir);
798     ok((ret > 0) && (ret <= MAX_PATH),
799        "GetTempPathA error: %d\n", GetLastError());
800
801     ret = GetTempFileNameA(workdir, "fcn", 0, tempfile);
802     ok(ret, "GetTempFileNameA error: %d\n", GetLastError());
803     ret = DeleteFileA(tempfile);
804     ok(ret, "DeleteFileA error: %d\n", GetLastError());
805
806     lstrcpyA(parentdir, tempfile);
807     ret = CreateDirectoryA(parentdir, NULL);
808     ok(ret, "CreateDirectoryA error: %d\n", GetLastError());
809
810     lstrcpyA(childdir, parentdir);
811     lstrcatA(childdir, "\\c");
812     ret = CreateDirectoryA(childdir, NULL);
813     ok(ret, "CreateDirectoryA error: %d\n", GetLastError());
814
815
816     /* When recursively watching overlapping directories, changes in child
817      * should trigger notifications for both child and parent */
818     parent_thread = StartNotificationThread(parentdir, TRUE,
819                                             FILE_NOTIFY_CHANGE_FILE_NAME);
820     child_thread = StartNotificationThread(childdir, TRUE,
821                                             FILE_NOTIFY_CHANGE_FILE_NAME);
822
823     /* Create a file in child */
824     ret = GetTempFileNameA(childdir, "fcn", 0, tempfile);
825     ok(ret, "GetTempFileNameA error: %d\n", GetLastError());
826
827     /* Both watches should trigger */
828     ret = FinishNotificationThread(parent_thread);
829     ok(ret, "Missed parent notification\n");
830     ret = FinishNotificationThread(child_thread);
831     ok(ret, "Missed child notification\n");
832
833     ret = DeleteFileA(tempfile);
834     ok(ret, "DeleteFileA error: %d\n", GetLastError());
835
836
837     /* Removing a recursive parent watch should not affect child watches. Doing
838      * so used to crash wineserver. */
839     parent_watch = FindFirstChangeNotificationA(parentdir, TRUE,
840                                                 FILE_NOTIFY_CHANGE_FILE_NAME);
841     ok(parent_watch != INVALID_HANDLE_VALUE,
842        "FindFirstChangeNotification error: %d\n", GetLastError());
843     child_watch = FindFirstChangeNotificationA(childdir, TRUE,
844                                                FILE_NOTIFY_CHANGE_FILE_NAME);
845     ok(child_watch != INVALID_HANDLE_VALUE,
846        "FindFirstChangeNotification error: %d\n", GetLastError());
847
848     ret = FindCloseChangeNotification(parent_watch);
849     ok(ret, "FindCloseChangeNotification error: %d\n", GetLastError());
850
851     child_thread = CreateThread(NULL, 0, NotificationThread, child_watch, 0,
852                                 &threadId);
853     ok(child_thread != NULL, "CreateThread error: %d\n", GetLastError());
854
855     /* Create a file in child */
856     ret = GetTempFileNameA(childdir, "fcn", 0, tempfile);
857     ok(ret, "GetTempFileNameA error: %d\n", GetLastError());
858
859     /* Child watch should trigger */
860     ret = FinishNotificationThread(child_thread);
861     ok(ret, "Missed child notification\n");
862
863     /* clean up */
864     ret = DeleteFileA(tempfile);
865     ok(ret, "DeleteFileA error: %d\n", GetLastError());
866
867     ret = RemoveDirectoryA(childdir);
868     ok(ret, "RemoveDirectoryA error: %d\n", GetLastError());
869
870     ret = RemoveDirectoryA(parentdir);
871     ok(ret, "RemoveDirectoryA error: %d\n", GetLastError());
872 }
873
874 START_TEST(change)
875 {
876     HMODULE hkernel32 = GetModuleHandle("kernel32");
877     pReadDirectoryChangesW = (fnReadDirectoryChangesW)
878         GetProcAddress(hkernel32, "ReadDirectoryChangesW");
879
880     test_ffcnMultipleThreads();
881     /* The above function runs a test that must occur before FindCloseChangeNotification is run in the
882        current thread to preserve the emptiness of the wine user APC queue. To ensure this it should be
883        placed first. */
884     test_FindFirstChangeNotification();
885     test_ffcn();
886     test_readdirectorychanges();
887     test_readdirectorychanges_null();
888     test_readdirectorychanges_filedir();
889     test_ffcn_directory_overlap();
890 }