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