kernel32: Fix two test failures on Win98 due to missing GetTempPathW.
[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     SetLastError(0xdeadbeef);
391     r = GetTempPathW( MAX_PATH, path );
392     if (!r && (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED))
393     {
394         skip("GetTempPathW is not implemented\n");
395         return;
396     }
397     ok( r != 0, "temp path failed\n");
398     if (!r)
399         return;
400
401     lstrcatW( path, szBoo );
402     lstrcpyW( subdir, path );
403     lstrcatW( subdir, szHoo );
404
405     lstrcpyW( subsubdir, path );
406     lstrcatW( subsubdir, szGa );
407
408     RemoveDirectoryW( subsubdir );
409     RemoveDirectoryW( subdir );
410     RemoveDirectoryW( path );
411     
412     r = CreateDirectoryW(path, NULL);
413     ok( r == TRUE, "failed to create directory\n");
414
415     SetLastError(0xd0b00b00);
416     r = pReadDirectoryChangesW(NULL,NULL,0,FALSE,0,NULL,NULL,NULL);
417     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
418     ok(r==FALSE, "should return false\n");
419
420     fflags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
421     hdir = CreateFileW(path, GENERIC_READ|SYNCHRONIZE|FILE_LIST_DIRECTORY, 
422                         FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, 
423                         OPEN_EXISTING, fflags, NULL);
424     ok( hdir != INVALID_HANDLE_VALUE, "failed to open directory\n");
425
426     ov.hEvent = CreateEvent( NULL, 1, 0, NULL );
427
428     SetLastError(0xd0b00b00);
429     r = pReadDirectoryChangesW(hdir,NULL,0,FALSE,0,NULL,NULL,NULL);
430     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
431     ok(r==FALSE, "should return false\n");
432
433     SetLastError(0xd0b00b00);
434     r = pReadDirectoryChangesW(hdir,NULL,0,FALSE,0,NULL,&ov,NULL);
435     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
436     ok(r==FALSE, "should return false\n");
437
438     filter = FILE_NOTIFY_CHANGE_FILE_NAME;
439     filter |= FILE_NOTIFY_CHANGE_DIR_NAME;
440     filter |= FILE_NOTIFY_CHANGE_ATTRIBUTES;
441     filter |= FILE_NOTIFY_CHANGE_SIZE;
442     filter |= FILE_NOTIFY_CHANGE_LAST_WRITE;
443     filter |= FILE_NOTIFY_CHANGE_LAST_ACCESS;
444     filter |= FILE_NOTIFY_CHANGE_CREATION;
445     filter |= FILE_NOTIFY_CHANGE_SECURITY;
446
447     SetLastError(0xd0b00b00);
448     ov.Internal = 0;
449     ov.InternalHigh = 0;
450     memset( buffer, 0, sizeof buffer );
451
452     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,-1,NULL,&ov,NULL);
453     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
454     ok(r==FALSE, "should return false\n");
455
456     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,0,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,TRUE,filter,NULL,&ov,NULL);
461     ok(r==TRUE, "should return true\n");
462
463     r = WaitForSingleObject( ov.hEvent, 10 );
464     ok( r == STATUS_TIMEOUT, "should timeout\n" );
465
466     r = CreateDirectoryW( subdir, NULL );
467     ok( r == TRUE, "failed to create directory\n");
468
469     r = WaitForSingleObject( ov.hEvent, 1000 );
470     ok( r == WAIT_OBJECT_0, "event should be ready\n" );
471
472     ok( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
473     ok( ov.InternalHigh == 0x12, "ov.InternalHigh wrong\n");
474
475     pfni = (PFILE_NOTIFY_INFORMATION) buffer;
476     ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
477     ok( pfni->Action == FILE_ACTION_ADDED, "action wrong\n" );
478     ok( pfni->FileNameLength == 6, "len wrong\n" );
479     ok( !memcmp(pfni->FileName,&szHoo[1],6), "name wrong\n" );
480
481     ResetEvent(ov.hEvent);
482     SetLastError(0xd0b00b00);
483     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,0,NULL,NULL,NULL);
484     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
485     ok(r==FALSE, "should return false\n");
486
487     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,0,NULL,&ov,NULL);
488     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
489     ok(r==FALSE, "should return false\n");
490
491     filter = FILE_NOTIFY_CHANGE_SIZE;
492
493     SetEvent(ov.hEvent);
494     ov.Internal = 1;
495     ov.InternalHigh = 1;
496     S(U(ov)).Offset = 0;
497     S(U(ov)).OffsetHigh = 0;
498     memset( buffer, 0, sizeof buffer );
499     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
500     ok(r==TRUE, "should return true\n");
501
502     ok( ov.Internal == STATUS_PENDING, "ov.Internal wrong\n");
503     ok( ov.InternalHigh == 1, "ov.InternalHigh wrong\n");
504
505     r = WaitForSingleObject( ov.hEvent, 0 );
506     ok( r == STATUS_TIMEOUT, "should timeout\n" );
507
508     r = RemoveDirectoryW( subdir );
509     ok( r == TRUE, "failed to remove directory\n");
510
511     r = WaitForSingleObject( ov.hEvent, 1000 );
512     ok( r == WAIT_OBJECT_0, "should be ready\n" );
513
514     ok( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
515     ok( ov.InternalHigh == 0x12, "ov.InternalHigh wrong\n");
516
517     if (ov.Internal == STATUS_SUCCESS)
518     {
519         r = GetOverlappedResult( hdir, &ov, &dwCount, TRUE );
520         ok( r == TRUE, "getoverlappedresult failed\n");
521         ok( dwCount == 0x12, "count wrong\n");
522     }
523
524     pfni = (PFILE_NOTIFY_INFORMATION) buffer;
525     ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
526     ok( pfni->Action == FILE_ACTION_REMOVED, "action wrong\n" );
527     ok( pfni->FileNameLength == 6, "len wrong\n" );
528     ok( !memcmp(pfni->FileName,&szHoo[1],6), "name wrong\n" );
529
530     /* what happens if the buffer is too small? */
531     r = pReadDirectoryChangesW(hdir,buffer,0x10,FALSE,filter,NULL,&ov,NULL);
532     ok(r==TRUE, "should return true\n");
533
534     r = CreateDirectoryW( subdir, NULL );
535     ok( r == TRUE, "failed to create directory\n");
536
537     r = WaitForSingleObject( ov.hEvent, 1000 );
538     ok( r == WAIT_OBJECT_0, "should be ready\n" );
539
540     ok( ov.Internal == STATUS_NOTIFY_ENUM_DIR, "ov.Internal wrong\n");
541     ok( ov.InternalHigh == 0, "ov.InternalHigh wrong\n");
542
543     /* test the recursive watch */
544     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
545     ok(r==TRUE, "should return true\n");
546
547     r = CreateDirectoryW( subsubdir, NULL );
548     ok( r == TRUE, "failed to create directory\n");
549
550     r = WaitForSingleObject( ov.hEvent, 1000 );
551     ok( r == WAIT_OBJECT_0, "should be ready\n" );
552
553     ok( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
554     ok( ov.InternalHigh == 0x18, "ov.InternalHigh wrong\n");
555
556     pfni = (PFILE_NOTIFY_INFORMATION) buffer;
557     ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
558     ok( pfni->Action == FILE_ACTION_ADDED, "action wrong\n" );
559     ok( pfni->FileNameLength == 0x0c, "len wrong\n" );
560     ok( !memcmp(pfni->FileName,&szGa[1],6), "name wrong\n" );
561
562     r = RemoveDirectoryW( subsubdir );
563     ok( r == TRUE, "failed to remove directory\n");
564
565     ov.Internal = 1;
566     ov.InternalHigh = 1;
567     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
568     ok(r==TRUE, "should return true\n");
569
570     r = RemoveDirectoryW( subdir );
571     ok( r == TRUE, "failed to remove directory\n");
572
573     r = WaitForSingleObject( ov.hEvent, 1000 );
574     ok( r == WAIT_OBJECT_0, "should be ready\n" );
575
576     pfni = (PFILE_NOTIFY_INFORMATION) buffer;
577     ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
578     ok( pfni->Action == FILE_ACTION_REMOVED, "action wrong\n" );
579     ok( pfni->FileNameLength == 0x0c, "len wrong\n" );
580     ok( !memcmp(pfni->FileName,&szGa[1],6), "name wrong\n" );
581
582     ok( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
583     ok( ov.InternalHigh == 0x18, "ov.InternalHigh wrong\n");
584
585     CloseHandle(hdir);
586
587     r = RemoveDirectoryW( path );
588     ok( r == TRUE, "failed to remove directory\n");
589 }
590
591 /* show the behaviour when a null buffer is passed */
592 static void test_readdirectorychanges_null(void)
593 {
594     NTSTATUS r;
595     HANDLE hdir;
596     char buffer[0x1000];
597     DWORD fflags, filter = 0;
598     OVERLAPPED ov;
599     WCHAR path[MAX_PATH], subdir[MAX_PATH];
600     static const WCHAR szBoo[] = { '\\','b','o','o',0 };
601     static const WCHAR szHoo[] = { '\\','h','o','o',0 };
602     PFILE_NOTIFY_INFORMATION pfni;
603
604     if (!pReadDirectoryChangesW)
605     {
606         skip("ReadDirectoryChangesW is not available\n");
607         return;
608     }
609     SetLastError(0xdeadbeef);
610     r = GetTempPathW( MAX_PATH, path );
611     if (!r && (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED))
612     {
613         skip("GetTempPathW is not implemented\n");
614         return;
615     }
616     ok( r != 0, "temp path failed\n");
617     if (!r)
618         return;
619
620     lstrcatW( path, szBoo );
621     lstrcpyW( subdir, path );
622     lstrcatW( subdir, szHoo );
623
624     RemoveDirectoryW( subdir );
625     RemoveDirectoryW( path );
626     
627     r = CreateDirectoryW(path, NULL);
628     ok( r == TRUE, "failed to create directory\n");
629
630     fflags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
631     hdir = CreateFileW(path, GENERIC_READ|SYNCHRONIZE|FILE_LIST_DIRECTORY, 
632                         FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, 
633                         OPEN_EXISTING, fflags, NULL);
634     ok( hdir != INVALID_HANDLE_VALUE, "failed to open directory\n");
635
636     ov.hEvent = CreateEvent( NULL, 1, 0, NULL );
637
638     filter = FILE_NOTIFY_CHANGE_FILE_NAME;
639     filter |= FILE_NOTIFY_CHANGE_DIR_NAME;
640
641     SetLastError(0xd0b00b00);
642     ov.Internal = 0;
643     ov.InternalHigh = 0;
644     memset( buffer, 0, sizeof buffer );
645
646     r = pReadDirectoryChangesW(hdir,NULL,0,FALSE,filter,NULL,&ov,NULL);
647     ok(r==TRUE, "should return true\n");
648
649     r = WaitForSingleObject( ov.hEvent, 0 );
650     ok( r == STATUS_TIMEOUT, "should timeout\n" );
651
652     r = CreateDirectoryW( subdir, NULL );
653     ok( r == TRUE, "failed to create directory\n");
654
655     r = WaitForSingleObject( ov.hEvent, 0 );
656     ok( r == WAIT_OBJECT_0, "event should be ready\n" );
657
658     ok( ov.Internal == STATUS_NOTIFY_ENUM_DIR, "ov.Internal wrong\n");
659     ok( ov.InternalHigh == 0, "ov.InternalHigh wrong\n");
660
661     ov.Internal = 0;
662     ov.InternalHigh = 0;
663     S(U(ov)).Offset = 0;
664     S(U(ov)).OffsetHigh = 0;
665     memset( buffer, 0, sizeof buffer );
666
667     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
668     ok(r==TRUE, "should return true\n");
669
670     r = WaitForSingleObject( ov.hEvent, 0 );
671     ok( r == STATUS_TIMEOUT, "should timeout\n" );
672
673     r = RemoveDirectoryW( subdir );
674     ok( r == TRUE, "failed to remove directory\n");
675
676     r = WaitForSingleObject( ov.hEvent, 1000 );
677     ok( r == WAIT_OBJECT_0, "should be ready\n" );
678
679     ok( ov.Internal == STATUS_NOTIFY_ENUM_DIR, "ov.Internal wrong\n");
680     ok( ov.InternalHigh == 0, "ov.InternalHigh wrong\n");
681
682     pfni = (PFILE_NOTIFY_INFORMATION) buffer;
683     ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
684
685     CloseHandle(hdir);
686
687     r = RemoveDirectoryW( path );
688     ok( r == TRUE, "failed to remove directory\n");
689 }
690
691 static void test_readdirectorychanges_filedir(void)
692 {
693     NTSTATUS r;
694     HANDLE hdir, hfile;
695     char buffer[0x1000];
696     DWORD fflags, filter = 0;
697     OVERLAPPED ov;
698     WCHAR path[MAX_PATH], subdir[MAX_PATH], file[MAX_PATH];
699     static const WCHAR szBoo[] = { '\\','b','o','o',0 };
700     static const WCHAR szHoo[] = { '\\','h','o','o',0 };
701     static const WCHAR szFoo[] = { '\\','f','o','o',0 };
702     PFILE_NOTIFY_INFORMATION pfni;
703
704     SetLastError(0xdeadbeef);
705     r = GetTempPathW( MAX_PATH, path );
706     if (!r && (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED))
707     {
708         skip("GetTempPathW is not implemented\n");
709         return;
710     }
711     ok( r != 0, "temp path failed\n");
712     if (!r)
713         return;
714
715     lstrcatW( path, szBoo );
716     lstrcpyW( subdir, path );
717     lstrcatW( subdir, szHoo );
718
719     lstrcpyW( file, path );
720     lstrcatW( file, szFoo );
721
722     DeleteFileW( file );
723     RemoveDirectoryW( subdir );
724     RemoveDirectoryW( path );
725     
726     r = CreateDirectoryW(path, NULL);
727     ok( r == TRUE, "failed to create directory\n");
728
729     fflags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
730     hdir = CreateFileW(path, GENERIC_READ|SYNCHRONIZE|FILE_LIST_DIRECTORY, 
731                         FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, 
732                         OPEN_EXISTING, fflags, NULL);
733     ok( hdir != INVALID_HANDLE_VALUE, "failed to open directory\n");
734
735     ov.hEvent = CreateEvent( NULL, 0, 0, NULL );
736
737     filter = FILE_NOTIFY_CHANGE_FILE_NAME;
738
739     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,TRUE,filter,NULL,&ov,NULL);
740     ok(r==TRUE, "should return true\n");
741
742     r = WaitForSingleObject( ov.hEvent, 10 );
743     ok( r == WAIT_TIMEOUT, "should timeout\n" );
744
745     r = CreateDirectoryW( subdir, NULL );
746     ok( r == TRUE, "failed to create directory\n");
747
748     hfile = CreateFileW( file, GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL );
749     ok( hfile != INVALID_HANDLE_VALUE, "failed to create file\n");
750     ok( CloseHandle(hfile), "failed toc lose file\n");
751
752     r = WaitForSingleObject( ov.hEvent, 1000 );
753     ok( r == WAIT_OBJECT_0, "event should be ready\n" );
754
755     ok( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
756     ok( ov.InternalHigh == 0x12, "ov.InternalHigh wrong\n");
757
758     pfni = (PFILE_NOTIFY_INFORMATION) buffer;
759     ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
760     ok( pfni->Action == FILE_ACTION_ADDED, "action wrong\n" );
761     ok( pfni->FileNameLength == 6, "len wrong\n" );
762     ok( !memcmp(pfni->FileName,&szFoo[1],6), "name wrong\n" );
763
764     r = DeleteFileW( file );
765     ok( r == TRUE, "failed to delete file\n");
766
767     r = RemoveDirectoryW( subdir );
768     ok( r == TRUE, "failed to remove directory\n");
769
770     CloseHandle(hdir);
771
772     r = RemoveDirectoryW( path );
773     ok( r == TRUE, "failed to remove directory\n");
774 }
775
776 START_TEST(change)
777 {
778     HMODULE hkernel32 = GetModuleHandle("kernel32");
779     pReadDirectoryChangesW = (fnReadDirectoryChangesW)
780         GetProcAddress(hkernel32, "ReadDirectoryChangesW");
781
782     test_ffcnMultipleThreads();
783     /* The above function runs a test that must occur before FindCloseChangeNotification is run in the
784        current thread to preserve the emptiness of the wine user APC queue. To ensure this it should be
785        placed first. */
786     test_FindFirstChangeNotification();
787     test_ffcn();
788     test_readdirectorychanges();
789     test_readdirectorychanges_null();
790     test_readdirectorychanges_filedir();
791 }