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