kernel32: FindExSearchLimitToDirectories has no effect on FindFirstFileEx.
[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 != NULL, "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     SetLastError(0xdeadbeef);
260     r = GetTempPathW( MAX_PATH, path );
261     if (!r && (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED))
262     {
263         skip("GetTempPathW is not implemented\n");
264         return;
265     }
266     ok( r != 0, "temp path failed\n");
267     if (!r)
268         return;
269
270     lstrcatW( path, szBoo );
271     lstrcpyW( subdir, path );
272     lstrcatW( subdir, szHoo );
273
274     RemoveDirectoryW( subdir );
275     RemoveDirectoryW( path );
276     
277     r = CreateDirectoryW(path, NULL);
278     ok( r == TRUE, "failed to create directory\n");
279
280     filter = FILE_NOTIFY_CHANGE_FILE_NAME;
281     filter |= FILE_NOTIFY_CHANGE_DIR_NAME;
282
283     handle = FindFirstChangeNotificationW( path, 1, filter);
284     ok( handle != INVALID_HANDLE_VALUE, "invalid handle\n");
285
286     r = WaitForSingleObject( handle, 0 );
287     ok( r == STATUS_TIMEOUT, "should time out\n");
288
289     r = CreateDirectoryW( subdir, NULL );
290     ok( r == TRUE, "failed to create subdir\n");
291
292     r = WaitForSingleObject( handle, 0 );
293     ok( r == WAIT_OBJECT_0, "should be ready\n");
294
295     r = WaitForSingleObject( handle, 0 );
296     ok( r == WAIT_OBJECT_0, "should be ready\n");
297
298     r = FindNextChangeNotification(handle);
299     ok( r == TRUE, "find next failed\n");
300
301     r = WaitForSingleObject( handle, 0 );
302     ok( r == STATUS_TIMEOUT, "should time out\n");
303
304     r = RemoveDirectoryW( subdir );
305     ok( r == TRUE, "failed to remove subdir\n");
306
307     r = WaitForSingleObject( handle, 0 );
308     ok( r == WAIT_OBJECT_0, "should be ready\n");
309
310     r = WaitForSingleObject( handle, 0 );
311     ok( r == WAIT_OBJECT_0, "should be ready\n");
312
313     r = FindNextChangeNotification(handle);
314     ok( r == TRUE, "find next failed\n");
315
316     r = FindNextChangeNotification(handle);
317     ok( r == TRUE, "find next failed\n");
318
319     r = FindCloseChangeNotification(handle);
320     ok( r == TRUE, "should succeed\n");
321
322     r = RemoveDirectoryW( path );
323     ok( r == TRUE, "failed to remove dir\n");
324 }
325
326 /* this test concentrates on the wait behavior when multiple threads are
327  * waiting on a change notification handle. */
328 static void test_ffcnMultipleThreads(void)
329 {
330     LONG r;
331     DWORD filter, threadId, status, exitcode;
332     HANDLE handles[2];
333     char path[MAX_PATH];
334
335     r = GetTempPathA(MAX_PATH, path);
336     ok(r, "GetTempPathA error: %d\n", GetLastError());
337
338     lstrcatA(path, "ffcnTestMultipleThreads");
339
340     RemoveDirectoryA(path);
341
342     r = CreateDirectoryA(path, NULL);
343     ok(r, "CreateDirectoryA error: %d\n", GetLastError());
344
345     filter = FILE_NOTIFY_CHANGE_FILE_NAME;
346     filter |= FILE_NOTIFY_CHANGE_DIR_NAME;
347
348     handles[0] = FindFirstChangeNotificationA(path, FALSE, filter);
349     ok(handles[0] != INVALID_HANDLE_VALUE, "FindFirstChangeNotification error: %d\n", GetLastError());
350
351     /* Test behavior if a waiting thread holds the last reference to a change
352      * directory object with an empty wine user APC queue for this thread (bug #7286) */
353
354     /* Create our notification thread */
355     handles[1] = CreateThread(NULL, 0, NotificationThread, (LPVOID)handles[0],
356                               0, &threadId);
357     ok(handles[1] != NULL, "CreateThread error: %d\n", GetLastError());
358
359     status = WaitForMultipleObjects(2, handles, FALSE, 5000);
360     ok(status == WAIT_OBJECT_0 || status == WAIT_OBJECT_0+1, "WaitForMultipleObjects status %d error %d\n", status, GetLastError());
361     ok(GetExitCodeThread(handles[1], &exitcode), "Could not retrieve thread exit code\n");
362
363     /* Clean up */
364     r = RemoveDirectoryA( path );
365     ok( r == TRUE, "failed to remove dir\n");
366 }
367
368 typedef BOOL (WINAPI *fnReadDirectoryChangesW)(HANDLE,LPVOID,DWORD,BOOL,DWORD,
369                          LPDWORD,LPOVERLAPPED,LPOVERLAPPED_COMPLETION_ROUTINE);
370 fnReadDirectoryChangesW pReadDirectoryChangesW;
371
372 static void test_readdirectorychanges(void)
373 {
374     HANDLE hdir;
375     char buffer[0x1000];
376     DWORD fflags, filter = 0, r, dwCount;
377     OVERLAPPED ov;
378     WCHAR path[MAX_PATH], subdir[MAX_PATH], subsubdir[MAX_PATH];
379     static const WCHAR szBoo[] = { '\\','b','o','o',0 };
380     static const WCHAR szHoo[] = { '\\','h','o','o',0 };
381     static const WCHAR szGa[] = { '\\','h','o','o','\\','g','a',0 };
382     PFILE_NOTIFY_INFORMATION pfni;
383
384     if (!pReadDirectoryChangesW)
385     {
386         skip("ReadDirectoryChangesW is not available\n");
387         return;
388     }
389
390     r = GetTempPathW( MAX_PATH, path );
391     ok( r != 0, "temp path failed\n");
392     if (!r)
393         return;
394
395     lstrcatW( path, szBoo );
396     lstrcpyW( subdir, path );
397     lstrcatW( subdir, szHoo );
398
399     lstrcpyW( subsubdir, path );
400     lstrcatW( subsubdir, szGa );
401
402     RemoveDirectoryW( subsubdir );
403     RemoveDirectoryW( subdir );
404     RemoveDirectoryW( path );
405     
406     r = CreateDirectoryW(path, NULL);
407     ok( r == TRUE, "failed to create directory\n");
408
409     SetLastError(0xd0b00b00);
410     r = pReadDirectoryChangesW(NULL,NULL,0,FALSE,0,NULL,NULL,NULL);
411     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
412     ok(r==FALSE, "should return false\n");
413
414     fflags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
415     hdir = CreateFileW(path, GENERIC_READ|SYNCHRONIZE|FILE_LIST_DIRECTORY, 
416                         FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, 
417                         OPEN_EXISTING, fflags, NULL);
418     ok( hdir != INVALID_HANDLE_VALUE, "failed to open directory\n");
419
420     ov.hEvent = CreateEvent( NULL, 1, 0, NULL );
421
422     SetLastError(0xd0b00b00);
423     r = pReadDirectoryChangesW(hdir,NULL,0,FALSE,0,NULL,NULL,NULL);
424     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
425     ok(r==FALSE, "should return false\n");
426
427     SetLastError(0xd0b00b00);
428     r = pReadDirectoryChangesW(hdir,NULL,0,FALSE,0,NULL,&ov,NULL);
429     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
430     ok(r==FALSE, "should return false\n");
431
432     filter = FILE_NOTIFY_CHANGE_FILE_NAME;
433     filter |= FILE_NOTIFY_CHANGE_DIR_NAME;
434     filter |= FILE_NOTIFY_CHANGE_ATTRIBUTES;
435     filter |= FILE_NOTIFY_CHANGE_SIZE;
436     filter |= FILE_NOTIFY_CHANGE_LAST_WRITE;
437     filter |= FILE_NOTIFY_CHANGE_LAST_ACCESS;
438     filter |= FILE_NOTIFY_CHANGE_CREATION;
439     filter |= FILE_NOTIFY_CHANGE_SECURITY;
440
441     SetLastError(0xd0b00b00);
442     ov.Internal = 0;
443     ov.InternalHigh = 0;
444     memset( buffer, 0, sizeof buffer );
445
446     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,-1,NULL,&ov,NULL);
447     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
448     ok(r==FALSE, "should return false\n");
449
450     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,0,NULL,&ov,NULL);
451     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
452     ok(r==FALSE, "should return false\n");
453
454     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,TRUE,filter,NULL,&ov,NULL);
455     ok(r==TRUE, "should return true\n");
456
457     r = WaitForSingleObject( ov.hEvent, 10 );
458     ok( r == STATUS_TIMEOUT, "should timeout\n" );
459
460     r = CreateDirectoryW( subdir, NULL );
461     ok( r == TRUE, "failed to create directory\n");
462
463     r = WaitForSingleObject( ov.hEvent, 1000 );
464     ok( r == WAIT_OBJECT_0, "event should be ready\n" );
465
466     ok( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
467     ok( ov.InternalHigh == 0x12, "ov.InternalHigh wrong\n");
468
469     pfni = (PFILE_NOTIFY_INFORMATION) buffer;
470     ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
471     ok( pfni->Action == FILE_ACTION_ADDED, "action wrong\n" );
472     ok( pfni->FileNameLength == 6, "len wrong\n" );
473     ok( !memcmp(pfni->FileName,&szHoo[1],6), "name wrong\n" );
474
475     ResetEvent(ov.hEvent);
476     SetLastError(0xd0b00b00);
477     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,0,NULL,NULL,NULL);
478     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
479     ok(r==FALSE, "should return false\n");
480
481     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,0,NULL,&ov,NULL);
482     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
483     ok(r==FALSE, "should return false\n");
484
485     filter = FILE_NOTIFY_CHANGE_SIZE;
486
487     SetEvent(ov.hEvent);
488     ov.Internal = 1;
489     ov.InternalHigh = 1;
490     S(U(ov)).Offset = 0;
491     S(U(ov)).OffsetHigh = 0;
492     memset( buffer, 0, sizeof buffer );
493     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
494     ok(r==TRUE, "should return true\n");
495
496     ok( ov.Internal == STATUS_PENDING, "ov.Internal wrong\n");
497     ok( ov.InternalHigh == 1, "ov.InternalHigh wrong\n");
498
499     r = WaitForSingleObject( ov.hEvent, 0 );
500     ok( r == STATUS_TIMEOUT, "should timeout\n" );
501
502     r = RemoveDirectoryW( subdir );
503     ok( r == TRUE, "failed to remove directory\n");
504
505     r = WaitForSingleObject( ov.hEvent, 1000 );
506     ok( r == WAIT_OBJECT_0, "should be ready\n" );
507
508     ok( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
509     ok( ov.InternalHigh == 0x12, "ov.InternalHigh wrong\n");
510
511     if (ov.Internal == STATUS_SUCCESS)
512     {
513         r = GetOverlappedResult( hdir, &ov, &dwCount, TRUE );
514         ok( r == TRUE, "getoverlappedresult failed\n");
515         ok( dwCount == 0x12, "count wrong\n");
516     }
517
518     pfni = (PFILE_NOTIFY_INFORMATION) buffer;
519     ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
520     ok( pfni->Action == FILE_ACTION_REMOVED, "action wrong\n" );
521     ok( pfni->FileNameLength == 6, "len wrong\n" );
522     ok( !memcmp(pfni->FileName,&szHoo[1],6), "name wrong\n" );
523
524     /* what happens if the buffer is too small? */
525     r = pReadDirectoryChangesW(hdir,buffer,0x10,FALSE,filter,NULL,&ov,NULL);
526     ok(r==TRUE, "should return true\n");
527
528     r = CreateDirectoryW( subdir, NULL );
529     ok( r == TRUE, "failed to create directory\n");
530
531     r = WaitForSingleObject( ov.hEvent, 1000 );
532     ok( r == WAIT_OBJECT_0, "should be ready\n" );
533
534     ok( ov.Internal == STATUS_NOTIFY_ENUM_DIR, "ov.Internal wrong\n");
535     ok( ov.InternalHigh == 0, "ov.InternalHigh wrong\n");
536
537     /* test the recursive watch */
538     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
539     ok(r==TRUE, "should return true\n");
540
541     r = CreateDirectoryW( subsubdir, NULL );
542     ok( r == TRUE, "failed to create directory\n");
543
544     r = WaitForSingleObject( ov.hEvent, 1000 );
545     ok( r == WAIT_OBJECT_0, "should be ready\n" );
546
547     ok( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
548     ok( ov.InternalHigh == 0x18, "ov.InternalHigh wrong\n");
549
550     pfni = (PFILE_NOTIFY_INFORMATION) buffer;
551     ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
552     ok( pfni->Action == FILE_ACTION_ADDED, "action wrong\n" );
553     ok( pfni->FileNameLength == 0x0c, "len wrong\n" );
554     ok( !memcmp(pfni->FileName,&szGa[1],6), "name wrong\n" );
555
556     r = RemoveDirectoryW( subsubdir );
557     ok( r == TRUE, "failed to remove directory\n");
558
559     ov.Internal = 1;
560     ov.InternalHigh = 1;
561     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
562     ok(r==TRUE, "should return true\n");
563
564     r = RemoveDirectoryW( subdir );
565     ok( r == TRUE, "failed to remove directory\n");
566
567     r = WaitForSingleObject( ov.hEvent, 1000 );
568     ok( r == WAIT_OBJECT_0, "should be ready\n" );
569
570     pfni = (PFILE_NOTIFY_INFORMATION) buffer;
571     ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
572     ok( pfni->Action == FILE_ACTION_REMOVED, "action wrong\n" );
573     ok( pfni->FileNameLength == 0x0c, "len wrong\n" );
574     ok( !memcmp(pfni->FileName,&szGa[1],6), "name wrong\n" );
575
576     ok( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
577     ok( ov.InternalHigh == 0x18, "ov.InternalHigh wrong\n");
578
579     CloseHandle(hdir);
580
581     r = RemoveDirectoryW( path );
582     ok( r == TRUE, "failed to remove directory\n");
583 }
584
585 /* show the behaviour when a null buffer is passed */
586 static void test_readdirectorychanges_null(void)
587 {
588     NTSTATUS r;
589     HANDLE hdir;
590     char buffer[0x1000];
591     DWORD fflags, filter = 0;
592     OVERLAPPED ov;
593     WCHAR path[MAX_PATH], subdir[MAX_PATH];
594     static const WCHAR szBoo[] = { '\\','b','o','o',0 };
595     static const WCHAR szHoo[] = { '\\','h','o','o',0 };
596     PFILE_NOTIFY_INFORMATION pfni;
597
598     if (!pReadDirectoryChangesW)
599     {
600         skip("ReadDirectoryChangesW is not available\n");
601         return;
602     }
603
604     r = GetTempPathW( MAX_PATH, path );
605     ok( r != 0, "temp path failed\n");
606     if (!r)
607         return;
608
609     lstrcatW( path, szBoo );
610     lstrcpyW( subdir, path );
611     lstrcatW( subdir, szHoo );
612
613     RemoveDirectoryW( subdir );
614     RemoveDirectoryW( path );
615     
616     r = CreateDirectoryW(path, NULL);
617     ok( r == TRUE, "failed to create directory\n");
618
619     fflags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
620     hdir = CreateFileW(path, GENERIC_READ|SYNCHRONIZE|FILE_LIST_DIRECTORY, 
621                         FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, 
622                         OPEN_EXISTING, fflags, NULL);
623     ok( hdir != INVALID_HANDLE_VALUE, "failed to open directory\n");
624
625     ov.hEvent = CreateEvent( NULL, 1, 0, NULL );
626
627     filter = FILE_NOTIFY_CHANGE_FILE_NAME;
628     filter |= FILE_NOTIFY_CHANGE_DIR_NAME;
629
630     SetLastError(0xd0b00b00);
631     ov.Internal = 0;
632     ov.InternalHigh = 0;
633     memset( buffer, 0, sizeof buffer );
634
635     r = pReadDirectoryChangesW(hdir,NULL,0,FALSE,filter,NULL,&ov,NULL);
636     ok(r==TRUE, "should return true\n");
637
638     r = WaitForSingleObject( ov.hEvent, 0 );
639     ok( r == STATUS_TIMEOUT, "should timeout\n" );
640
641     r = CreateDirectoryW( subdir, NULL );
642     ok( r == TRUE, "failed to create directory\n");
643
644     r = WaitForSingleObject( ov.hEvent, 0 );
645     ok( r == WAIT_OBJECT_0, "event should be ready\n" );
646
647     ok( ov.Internal == STATUS_NOTIFY_ENUM_DIR, "ov.Internal wrong\n");
648     ok( ov.InternalHigh == 0, "ov.InternalHigh wrong\n");
649
650     ov.Internal = 0;
651     ov.InternalHigh = 0;
652     S(U(ov)).Offset = 0;
653     S(U(ov)).OffsetHigh = 0;
654     memset( buffer, 0, sizeof buffer );
655
656     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
657     ok(r==TRUE, "should return true\n");
658
659     r = WaitForSingleObject( ov.hEvent, 0 );
660     ok( r == STATUS_TIMEOUT, "should timeout\n" );
661
662     r = RemoveDirectoryW( subdir );
663     ok( r == TRUE, "failed to remove directory\n");
664
665     r = WaitForSingleObject( ov.hEvent, 1000 );
666     ok( r == WAIT_OBJECT_0, "should be ready\n" );
667
668     ok( ov.Internal == STATUS_NOTIFY_ENUM_DIR, "ov.Internal wrong\n");
669     ok( ov.InternalHigh == 0, "ov.InternalHigh wrong\n");
670
671     pfni = (PFILE_NOTIFY_INFORMATION) buffer;
672     ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
673
674     CloseHandle(hdir);
675
676     r = RemoveDirectoryW( path );
677     ok( r == TRUE, "failed to remove directory\n");
678 }
679
680 static void test_readdirectorychanges_filedir(void)
681 {
682     NTSTATUS r;
683     HANDLE hdir, hfile;
684     char buffer[0x1000];
685     DWORD fflags, filter = 0;
686     OVERLAPPED ov;
687     WCHAR path[MAX_PATH], subdir[MAX_PATH], file[MAX_PATH];
688     static const WCHAR szBoo[] = { '\\','b','o','o',0 };
689     static const WCHAR szHoo[] = { '\\','h','o','o',0 };
690     static const WCHAR szFoo[] = { '\\','f','o','o',0 };
691     PFILE_NOTIFY_INFORMATION pfni;
692
693     SetLastError(0xdeadbeef);
694     r = GetTempPathW( MAX_PATH, path );
695     if (!r && (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED))
696     {
697         skip("GetTempPathW is not implemented\n");
698         return;
699     }
700     ok( r != 0, "temp path failed\n");
701     if (!r)
702         return;
703
704     lstrcatW( path, szBoo );
705     lstrcpyW( subdir, path );
706     lstrcatW( subdir, szHoo );
707
708     lstrcpyW( file, path );
709     lstrcatW( file, szFoo );
710
711     DeleteFileW( file );
712     RemoveDirectoryW( subdir );
713     RemoveDirectoryW( path );
714     
715     r = CreateDirectoryW(path, NULL);
716     ok( r == TRUE, "failed to create directory\n");
717
718     fflags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
719     hdir = CreateFileW(path, GENERIC_READ|SYNCHRONIZE|FILE_LIST_DIRECTORY, 
720                         FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, 
721                         OPEN_EXISTING, fflags, NULL);
722     ok( hdir != INVALID_HANDLE_VALUE, "failed to open directory\n");
723
724     ov.hEvent = CreateEvent( NULL, 0, 0, NULL );
725
726     filter = FILE_NOTIFY_CHANGE_FILE_NAME;
727
728     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,TRUE,filter,NULL,&ov,NULL);
729     ok(r==TRUE, "should return true\n");
730
731     r = WaitForSingleObject( ov.hEvent, 10 );
732     ok( r == WAIT_TIMEOUT, "should timeout\n" );
733
734     r = CreateDirectoryW( subdir, NULL );
735     ok( r == TRUE, "failed to create directory\n");
736
737     hfile = CreateFileW( file, GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL );
738     ok( hfile != INVALID_HANDLE_VALUE, "failed to create file\n");
739     ok( CloseHandle(hfile), "failed toc lose file\n");
740
741     r = WaitForSingleObject( ov.hEvent, 1000 );
742     ok( r == WAIT_OBJECT_0, "event should be ready\n" );
743
744     ok( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
745     ok( ov.InternalHigh == 0x12, "ov.InternalHigh wrong\n");
746
747     pfni = (PFILE_NOTIFY_INFORMATION) buffer;
748     ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
749     ok( pfni->Action == FILE_ACTION_ADDED, "action wrong\n" );
750     ok( pfni->FileNameLength == 6, "len wrong\n" );
751     ok( !memcmp(pfni->FileName,&szFoo[1],6), "name wrong\n" );
752
753     r = DeleteFileW( file );
754     ok( r == TRUE, "failed to delete file\n");
755
756     r = RemoveDirectoryW( subdir );
757     ok( r == TRUE, "failed to remove directory\n");
758
759     CloseHandle(hdir);
760
761     r = RemoveDirectoryW( path );
762     ok( r == TRUE, "failed to remove directory\n");
763 }
764
765 START_TEST(change)
766 {
767     HMODULE hkernel32 = GetModuleHandle("kernel32");
768     pReadDirectoryChangesW = (fnReadDirectoryChangesW)
769         GetProcAddress(hkernel32, "ReadDirectoryChangesW");
770
771     test_ffcnMultipleThreads();
772     /* The above function runs a test that must occur before FindCloseChangeNotification is run in the
773        current thread to preserve the emptiness of the wine user APC queue. To ensure this it should be
774        placed first. */
775     test_FindFirstChangeNotification();
776     test_ffcn();
777     test_readdirectorychanges();
778     test_readdirectorychanges_null();
779     test_readdirectorychanges_filedir();
780 }