Reactos
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
39static DWORD CALLBACK NotificationThread(LPVOID arg)
40{
41 HANDLE change = arg;
42 BOOL notified = FALSE;
43 BOOL ret = FALSE;
44 DWORD status;
45
46 status = WaitForSingleObject(change, 100);
47
48 if (status == WAIT_OBJECT_0 ) {
49 notified = TRUE;
50 FindNextChangeNotification(change);
51 }
52
53 ret = FindCloseChangeNotification(change);
54 ok( ret, "FindCloseChangeNotification error: %ld\n",
55 GetLastError());
56
57 ExitThread((DWORD)notified);
58}
59
60static 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: %ld\n", GetLastError());
67
68 thread = CreateThread(NULL, 0, NotificationThread, change, 0, &threadId);
69 ok(thread != NULL, "CreateThread error: %ld\n", GetLastError());
70
71 return thread;
72}
73
74static DWORD FinishNotificationThread(HANDLE thread)
75{
76 DWORD status, exitcode;
77
78 status = WaitForSingleObject(thread, 5000);
79 ok(status == WAIT_OBJECT_0, "WaitForSingleObject status %ld error %ld\n", status, GetLastError());
80
81 ok(GetExitCodeThread(thread, &exitcode), "Could not retrieve thread exit code\n");
82 CloseHandle(thread);
83
84 return exitcode;
85}
86
87static void test_FindFirstChangeNotification(void)
88{
89 HANDLE change, file, thread;
90 DWORD attributes, count;
91 BOOL ret;
92
93 char workdir[MAX_PATH], dirname1[MAX_PATH], dirname2[MAX_PATH];
94 char filename1[MAX_PATH], filename2[MAX_PATH];
95 static const char prefix[] = "FCN";
96 char buffer[2048];
97
98 /* pathetic checks */
99
100 change = FindFirstChangeNotificationA("not-a-file", FALSE, FILE_NOTIFY_CHANGE_FILE_NAME);
101 ok(change == INVALID_HANDLE_VALUE, "Expected INVALID_HANDLE_VALUE, got %p\n", change);
102 ok(GetLastError() == ERROR_FILE_NOT_FOUND,
103 "FindFirstChangeNotification error: %ld\n", GetLastError());
104
105 change = FindFirstChangeNotificationA(NULL, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME);
106 ok(change == INVALID_HANDLE_VALUE || broken(change == NULL) /* < win7 */,
107 "Expected INVALID_HANDLE_VALUE, got %p\n", change);
108 ok(GetLastError() == ERROR_PATH_NOT_FOUND,
109 "FindFirstChangeNotification error: %lu\n", GetLastError());
110
111 ret = FindNextChangeNotification(NULL);
112 ok(!ret && GetLastError() == ERROR_INVALID_HANDLE, "FindNextChangeNotification error: %ld\n",
113 GetLastError());
114
115 ret = FindCloseChangeNotification(NULL);
116 ok(!ret && GetLastError() == ERROR_INVALID_HANDLE, "FindCloseChangeNotification error: %ld\n",
117 GetLastError());
118
119 ret = GetTempPathA(MAX_PATH, dirname1);
120 ok(ret, "GetTempPathA error: %ld\n", GetLastError());
121
122 ret = GetTempFileNameA(dirname1, "ffc", 0, workdir);
123 ok(ret, "GetTempFileNameA error: %ld\n", GetLastError());
124 DeleteFileA( workdir );
125
126 ret = CreateDirectoryA(workdir, NULL);
127 ok(ret, "CreateDirectoryA error: %ld\n", GetLastError());
128
129 ret = GetTempFileNameA(workdir, prefix, 0, filename1);
130 ok(ret, "GetTempFileNameA error: %ld\n", GetLastError());
131
132 file = CreateFileA(filename1, GENERIC_WRITE|GENERIC_READ, 0, NULL, CREATE_ALWAYS,
133 FILE_ATTRIBUTE_NORMAL, 0);
134 ok(file != INVALID_HANDLE_VALUE, "CreateFileA error: %ld\n", GetLastError());
135 ret = CloseHandle(file);
136 ok( ret, "CloseHandle error: %ld\n", GetLastError());
137
138 /* Try to register notification for a file */
139 change = FindFirstChangeNotificationA(filename1, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME);
140 ok(change == INVALID_HANDLE_VALUE, "Expected INVALID_HANDLE_VALUE, got %p\n", change);
141 ok(GetLastError() == ERROR_DIRECTORY,
142 "FindFirstChangeNotification error: %ld\n", GetLastError());
143
144 lstrcpyA(dirname1, filename1);
145 lstrcatA(dirname1, "dir");
146
147 lstrcpyA(dirname2, dirname1);
148 lstrcatA(dirname2, "new");
149
150 ret = CreateDirectoryA(dirname1, NULL);
151 ok(ret, "CreateDirectoryA error: %ld\n", GetLastError());
152
153 /* What if we move the directory we registered notification for? */
154 thread = StartNotificationThread(dirname1, FALSE, FILE_NOTIFY_CHANGE_DIR_NAME);
155 ret = MoveFileA(dirname1, dirname2);
156 ok(ret, "MoveFileA error: %ld\n", GetLastError());
157 ok(!FinishNotificationThread(thread), "Got notification\n");
158
159 /* What if we remove the directory we registered notification for? */
160 thread = StartNotificationThread(dirname2, FALSE, FILE_NOTIFY_CHANGE_DIR_NAME);
161 ret = RemoveDirectoryA(dirname2);
162 ok(ret, "RemoveDirectoryA error: %ld\n", GetLastError());
163 ret = FinishNotificationThread(thread);
164 todo_wine ok(ret, "Missed notification\n");
165
166 /* functional checks */
167
168 /* Create a directory */
169 thread = StartNotificationThread(workdir, FALSE, FILE_NOTIFY_CHANGE_DIR_NAME);
170 ret = CreateDirectoryA(dirname1, NULL);
171 ok(ret, "CreateDirectoryA error: %ld\n", GetLastError());
172 ok(FinishNotificationThread(thread), "Missed notification\n");
173
174 /* Rename a directory */
175 thread = StartNotificationThread(workdir, FALSE, FILE_NOTIFY_CHANGE_DIR_NAME);
176 ret = MoveFileA(dirname1, dirname2);
177 ok(ret, "MoveFileA error: %ld\n", GetLastError());
178 ok(FinishNotificationThread(thread), "Missed notification\n");
179
180 /* Delete a directory */
181 thread = StartNotificationThread(workdir, FALSE, FILE_NOTIFY_CHANGE_DIR_NAME);
182 ret = RemoveDirectoryA(dirname2);
183 ok(ret, "RemoveDirectoryA error: %ld\n", GetLastError());
184 ok(FinishNotificationThread(thread), "Missed notification\n");
185
186 lstrcpyA(filename2, filename1);
187 lstrcatA(filename2, "new");
188
189 /* Rename a file */
190 thread = StartNotificationThread(workdir, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME);
191 ret = MoveFileA(filename1, filename2);
192 ok(ret, "MoveFileA error: %ld\n", GetLastError());
193 ok(FinishNotificationThread(thread), "Missed notification\n");
194
195 /* Delete a file */
196 thread = StartNotificationThread(workdir, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME);
197 ret = DeleteFileA(filename2);
198 ok(ret, "DeleteFileA error: %ld\n", GetLastError());
199 ok(FinishNotificationThread(thread), "Missed notification\n");
200
201 /* Create a file */
202 thread = StartNotificationThread(workdir, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME);
203 file = CreateFileA(filename2, GENERIC_WRITE|GENERIC_READ, 0, NULL, CREATE_ALWAYS,
204 FILE_ATTRIBUTE_NORMAL, 0);
205 ok(file != INVALID_HANDLE_VALUE, "CreateFileA error: %ld\n", GetLastError());
206 ret = CloseHandle(file);
207 ok( ret, "CloseHandle error: %ld\n", GetLastError());
208 ok(FinishNotificationThread(thread), "Missed notification\n");
209
210 attributes = GetFileAttributesA(filename2);
211 ok(attributes != INVALID_FILE_ATTRIBUTES, "GetFileAttributesA error: %ld\n", GetLastError());
212 attributes &= FILE_ATTRIBUTE_READONLY;
213
214 /* Change file attributes */
215 thread = StartNotificationThread(workdir, FALSE, FILE_NOTIFY_CHANGE_ATTRIBUTES);
216 ret = SetFileAttributesA(filename2, attributes);
217 ok(ret, "SetFileAttributesA error: %ld\n", GetLastError());
218 ok(FinishNotificationThread(thread), "Missed notification\n");
219
220 /* Change last write time by writing to a file */
221 thread = StartNotificationThread(workdir, FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE);
222 file = CreateFileA(filename2, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
223 FILE_ATTRIBUTE_NORMAL, 0);
224 ok(file != INVALID_HANDLE_VALUE, "CreateFileA error: %ld\n", GetLastError());
225 memset(buffer, 0, sizeof(buffer));
226 ret = WriteFile(file, buffer, sizeof(buffer), &count, NULL);
227 ok(ret && count == sizeof(buffer), "WriteFile error: %ld\n", GetLastError());
228 ret = CloseHandle(file);
229 ok( ret, "CloseHandle error: %ld\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: %ld\n", GetLastError());
237 ret = WriteFile(file, buffer, sizeof(buffer) / 2, &count, NULL);
238 ok(ret && count == sizeof(buffer) / 2, "WriteFileA error: %ld\n", GetLastError());
239 ret = CloseHandle(file);
240 ok( ret, "CloseHandle error: %ld\n", GetLastError());
241 ok(FinishNotificationThread(thread), "Missed notification\n");
242
243 /* clean up */
244
245 ret = DeleteFileA(filename2);
246 ok(ret, "DeleteFileA error: %ld\n", GetLastError());
247
248 ret = RemoveDirectoryA(workdir);
249 ok(ret, "RemoveDirectoryA error: %ld\n", GetLastError());
250}
251
252/* this test concentrates more on the wait behaviour of the handle */
253static void test_ffcn(void)
254{
255 DWORD filter;
256 HANDLE handle, file;
257 LONG r;
258 WCHAR path[MAX_PATH], subdir[MAX_PATH], filename[MAX_PATH];
259 static const WCHAR szBoo[] = { '\\','b','o','o',0 };
260 static const WCHAR szHoo[] = { '\\','h','o','o',0 };
261 static const WCHAR szZoo[] = { '\\','z','o','o',0 };
262
263 r = GetTempPathW( MAX_PATH, path );
264 ok( r != 0, "temp path failed\n");
265
266 lstrcatW( path, szBoo );
267 lstrcpyW( subdir, path );
268 lstrcatW( subdir, szHoo );
269
270 lstrcpyW( filename, path );
271 lstrcatW( filename, szZoo );
272
273 RemoveDirectoryW( subdir );
274 RemoveDirectoryW( path );
275
276 r = CreateDirectoryW(path, NULL);
277 ok( r == TRUE, "failed to create directory\n");
278
279 filter = FILE_NOTIFY_CHANGE_FILE_NAME;
280 filter |= FILE_NOTIFY_CHANGE_DIR_NAME;
281
282 handle = FindFirstChangeNotificationW( path, 1, filter);
283 ok( handle != INVALID_HANDLE_VALUE, "invalid handle\n");
284
285 r = WaitForSingleObject( handle, 0 );
286 ok( r == STATUS_TIMEOUT, "should time out\n");
287
288 file = CreateFileW( filename, GENERIC_READ | GENERIC_WRITE, 0, NULL,
289 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0 );
290 ok( file != INVALID_HANDLE_VALUE, "CreateFile error %lu\n", GetLastError() );
291 CloseHandle(file);
292
293 r = WaitForSingleObject( handle, 1000 );
294 ok( r == WAIT_OBJECT_0, "should be ready\n");
295
296 r = WaitForSingleObject( handle, 0 );
297 ok( r == WAIT_OBJECT_0, "should be ready\n");
298
299 r = FindNextChangeNotification(handle);
300 ok( r == TRUE, "find next failed\n");
301
302 r = WaitForSingleObject( handle, 0 );
303 ok( r == STATUS_TIMEOUT, "should time out\n");
304
305 r = DeleteFileW( filename );
306 ok( r == TRUE, "failed to remove file\n");
307
308 r = WaitForSingleObject( handle, 1000 );
309 ok( r == WAIT_OBJECT_0, "should be ready\n");
310
311 r = WaitForSingleObject( handle, 0 );
312 ok( r == WAIT_OBJECT_0, "should be ready\n");
313
314 r = FindNextChangeNotification(handle);
315 ok( r == TRUE, "find next failed\n");
316
317 r = WaitForSingleObject( handle, 0 );
318 ok( r == STATUS_TIMEOUT, "should time out\n");
319
320 r = CreateDirectoryW( subdir, NULL );
321 ok( r == TRUE, "failed to create subdir\n");
322
323 r = WaitForSingleObject( handle, 1000 );
324 ok( r == WAIT_OBJECT_0, "should be ready\n");
325
326 r = WaitForSingleObject( handle, 0 );
327 ok( r == WAIT_OBJECT_0, "should be ready\n");
328
329 r = FindNextChangeNotification(handle);
330 ok( r == TRUE, "find next failed\n");
331
332 r = WaitForSingleObject( handle, 0 );
333 ok( r == STATUS_TIMEOUT, "should time out\n");
334
335 r = RemoveDirectoryW( subdir );
336 ok( r == TRUE, "failed to remove subdir\n");
337
338 r = WaitForSingleObject( handle, 1000 );
339 ok( r == WAIT_OBJECT_0, "should be ready\n");
340
341 r = WaitForSingleObject( handle, 0 );
342 ok( r == WAIT_OBJECT_0, "should be ready\n");
343
344 r = FindNextChangeNotification(handle);
345 ok( r == TRUE, "find next failed\n");
346
347 r = FindNextChangeNotification(handle);
348 ok( r == TRUE, "find next failed\n");
349
350 r = FindCloseChangeNotification(handle);
351 ok( r == TRUE, "should succeed\n");
352
353 r = RemoveDirectoryW( path );
354 ok( r == TRUE, "failed to remove dir\n");
355}
356
357/* this test concentrates on the wait behavior when multiple threads are
358 * waiting on a change notification handle. */
359static void test_ffcnMultipleThreads(void)
360{
361 LONG r;
362 DWORD filter, threadId, status, exitcode;
363 HANDLE handles[2];
364 char tmp[MAX_PATH], path[MAX_PATH];
365
366 r = GetTempPathA(MAX_PATH, tmp);
367 ok(r, "GetTempPathA error: %ld\n", GetLastError());
368
369 r = GetTempFileNameA(tmp, "ffc", 0, path);
370 ok(r, "GetTempFileNameA error: %ld\n", GetLastError());
371 DeleteFileA( path );
372
373 r = CreateDirectoryA(path, NULL);
374 ok(r, "CreateDirectoryA error: %ld\n", GetLastError());
375
376 filter = FILE_NOTIFY_CHANGE_FILE_NAME;
377 filter |= FILE_NOTIFY_CHANGE_DIR_NAME;
378
379 handles[0] = FindFirstChangeNotificationA(path, FALSE, filter);
380 ok(handles[0] != INVALID_HANDLE_VALUE, "FindFirstChangeNotification error: %ld\n", GetLastError());
381
382 /* Test behavior if a waiting thread holds the last reference to a change
383 * directory object with an empty wine user APC queue for this thread (bug #7286) */
384
385 /* Create our notification thread */
386 handles[1] = CreateThread(NULL, 0, NotificationThread, handles[0], 0,
387 &threadId);
388 ok(handles[1] != NULL, "CreateThread error: %ld\n", GetLastError());
389
390 status = WaitForMultipleObjects(2, handles, FALSE, 5000);
391 ok(status == WAIT_OBJECT_0 || status == WAIT_OBJECT_0+1, "WaitForMultipleObjects status %ld error %ld\n", status, GetLastError());
392 ok(GetExitCodeThread(handles[1], &exitcode), "Could not retrieve thread exit code\n");
393
394 /* Clean up */
395 r = RemoveDirectoryA( path );
396 ok( r == TRUE, "failed to remove dir\n");
397}
398
399static void test_readdirectorychanges(void)
400{
401 HANDLE hdir;
402 char buffer[0x1000];
403 DWORD fflags, filter = 0, r, dwCount;
404 OVERLAPPED ov;
405 WCHAR path[MAX_PATH], subdir[MAX_PATH], subsubdir[MAX_PATH];
406 static const WCHAR szBoo[] = { '\\','b','o','o',0 };
407 static const WCHAR szHoo[] = { '\\','h','o','o',0 };
408 static const WCHAR szGa[] = { '\\','h','o','o','\\','g','a',0 };
409 PFILE_NOTIFY_INFORMATION pfni;
410 BOOL got_subdir_change = FALSE;
411
412 r = GetTempPathW( MAX_PATH, path );
413 ok( r != 0, "temp path failed\n");
414
415 lstrcatW( path, szBoo );
416 lstrcpyW( subdir, path );
417 lstrcatW( subdir, szHoo );
418
419 lstrcpyW( subsubdir, path );
420 lstrcatW( subsubdir, szGa );
421
422 RemoveDirectoryW( subsubdir );
423 RemoveDirectoryW( subdir );
424 RemoveDirectoryW( path );
425
426 r = CreateDirectoryW(path, NULL);
427 ok( r == TRUE, "failed to create directory\n");
428
429 SetLastError(0xd0b00b00);
430 r = ReadDirectoryChangesW(NULL,NULL,0,FALSE,0,NULL,NULL,NULL);
431 ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
432 ok(r==FALSE, "should return false\n");
433
434 fflags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
435 hdir = CreateFileW(path, GENERIC_READ|SYNCHRONIZE|FILE_LIST_DIRECTORY,
436 FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
437 OPEN_EXISTING, fflags, NULL);
438 ok( hdir != INVALID_HANDLE_VALUE, "failed to open directory\n");
439
440 ov.hEvent = CreateEventW( NULL, 1, 0, NULL );
441
442 SetLastError(0xd0b00b00);
443 r = ReadDirectoryChangesW(hdir,NULL,0,FALSE,0,NULL,NULL,NULL);
444 ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
445 ok(r==FALSE, "should return false\n");
446
447 SetLastError(0xd0b00b00);
448 r = ReadDirectoryChangesW(hdir,NULL,0,FALSE,0,NULL,&ov,NULL);
449 ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
450 ok(r==FALSE, "should return false\n");
451
452 filter = FILE_NOTIFY_CHANGE_FILE_NAME;
453 filter |= FILE_NOTIFY_CHANGE_DIR_NAME;
454 filter |= FILE_NOTIFY_CHANGE_ATTRIBUTES;
455 filter |= FILE_NOTIFY_CHANGE_SIZE;
456 filter |= FILE_NOTIFY_CHANGE_LAST_WRITE;
457 filter |= FILE_NOTIFY_CHANGE_LAST_ACCESS;
458 filter |= FILE_NOTIFY_CHANGE_CREATION;
459 filter |= FILE_NOTIFY_CHANGE_SECURITY;
460
461 SetLastError(0xd0b00b00);
462 ov.Internal = 0;
463 ov.InternalHigh = 0;
464 memset( buffer, 0, sizeof buffer );
465
466 r = ReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,-1,NULL,&ov,NULL);
467 ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
468 ok(r==FALSE, "should return false\n");
469
470 r = ReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,0,NULL,&ov,NULL);
471 ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
472 ok(r==FALSE, "should return false\n");
473
474 r = ReadDirectoryChangesW(hdir,buffer,sizeof buffer,TRUE,filter,NULL,&ov,NULL);
475 ok(r==TRUE, "should return true\n");
476
477 r = WaitForSingleObject( ov.hEvent, 10 );
478 ok( r == STATUS_TIMEOUT, "should timeout\n" );
479
480 r = CreateDirectoryW( subdir, NULL );
481 ok( r == TRUE, "failed to create directory\n");
482
483 r = WaitForSingleObject( ov.hEvent, 1000 );
484 ok( r == WAIT_OBJECT_0, "event should be ready\n" );
485
486 ok( (NTSTATUS)ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
487 ok( ov.InternalHigh == 0x12, "ov.InternalHigh wrong\n");
488
489 pfni = (PFILE_NOTIFY_INFORMATION) buffer;
490 ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
491 ok( pfni->Action == FILE_ACTION_ADDED, "action wrong\n" );
492 ok( pfni->FileNameLength == 6, "len wrong\n" );
493 ok( !memcmp(pfni->FileName,&szHoo[1],6), "name wrong\n" );
494
495 ResetEvent(ov.hEvent);
496 SetLastError(0xd0b00b00);
497 r = ReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,0,NULL,NULL,NULL);
498 ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
499 ok(r==FALSE, "should return false\n");
500
501 r = ReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,0,NULL,&ov,NULL);
502 ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
503 ok(r==FALSE, "should return false\n");
504
505 filter = FILE_NOTIFY_CHANGE_SIZE;
506
507 SetEvent(ov.hEvent);
508 ov.Internal = 1;
509 ov.InternalHigh = 1;
510 ov.Offset = 0;
511 ov.OffsetHigh = 0;
512 memset( buffer, 0, sizeof buffer );
513 r = ReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
514 ok(r==TRUE, "should return true\n");
515
516 ok( (NTSTATUS)ov.Internal == STATUS_PENDING, "ov.Internal wrong\n");
517 ok( ov.InternalHigh == 1, "ov.InternalHigh wrong\n");
518
519 r = WaitForSingleObject( ov.hEvent, 0 );
520 ok( r == STATUS_TIMEOUT, "should timeout\n" );
521
522 r = RemoveDirectoryW( subdir );
523 ok( r == TRUE, "failed to remove directory\n");
524
525 r = WaitForSingleObject( ov.hEvent, 1000 );
526 ok( r == WAIT_OBJECT_0, "should be ready\n" );
527
528 ok( (NTSTATUS)ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
529 ok( ov.InternalHigh == 0x12, "ov.InternalHigh wrong\n");
530
531 if ((NTSTATUS)ov.Internal == STATUS_SUCCESS)
532 {
533 r = GetOverlappedResult( hdir, &ov, &dwCount, TRUE );
534 ok( r == TRUE, "getoverlappedresult failed\n");
535 ok( dwCount == 0x12, "count wrong\n");
536 }
537
538 pfni = (PFILE_NOTIFY_INFORMATION) buffer;
539 ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
540 ok( pfni->Action == FILE_ACTION_REMOVED, "action wrong\n" );
541 ok( pfni->FileNameLength == 6, "len wrong\n" );
542 ok( !memcmp(pfni->FileName,&szHoo[1],6), "name wrong\n" );
543
544 /* what happens if the buffer is too small? */
545 r = ReadDirectoryChangesW(hdir,buffer,0x10,FALSE,filter,NULL,&ov,NULL);
546 ok(r==TRUE, "should return true\n");
547
548 r = CreateDirectoryW( subdir, NULL );
549 ok( r == TRUE, "failed to create directory\n");
550
551 r = WaitForSingleObject( ov.hEvent, 1000 );
552 ok( r == WAIT_OBJECT_0, "should be ready\n" );
553
554 ok( (NTSTATUS)ov.Internal == STATUS_NOTIFY_ENUM_DIR, "ov.Internal wrong\n");
555 ok( ov.InternalHigh == 0, "ov.InternalHigh wrong\n");
556
557 /* test the recursive watch */
558 r = ReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
559 ok(r==TRUE, "should return true\n");
560
561 r = CreateDirectoryW( subsubdir, NULL );
562 ok( r == TRUE, "failed to create directory\n");
563
564 while (1)
565 {
566 r = WaitForSingleObject( ov.hEvent, 1000 );
567 ok(r == WAIT_OBJECT_0, "should be ready\n" );
568 if (r == WAIT_TIMEOUT) break;
569
570 ok((NTSTATUS) ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
571
572 pfni = (PFILE_NOTIFY_INFORMATION) buffer;
573 while (1)
574 {
575 /* We might get one or more modified events on the parent dir */
576 if (pfni->Action == FILE_ACTION_MODIFIED)
577 {
578 ok(pfni->FileNameLength == 3 * sizeof(WCHAR), "len wrong\n" );
579 ok(!memcmp(pfni->FileName, &szGa[1], 3 * sizeof(WCHAR)), "name wrong\n");
580 }
581 else
582 {
583 ok(pfni->Action == FILE_ACTION_ADDED, "action wrong\n");
584 ok(pfni->FileNameLength == 6 * sizeof(WCHAR), "len wrong\n" );
585 ok(!memcmp(pfni->FileName, &szGa[1], 6 * sizeof(WCHAR)), "name wrong\n");
586 got_subdir_change = TRUE;
587 }
588 if (!pfni->NextEntryOffset) break;
589 pfni = (PFILE_NOTIFY_INFORMATION)((char *)pfni + pfni->NextEntryOffset);
590 }
591
592 if (got_subdir_change) break;
593
594 r = ReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
595 ok(r==TRUE, "should return true\n");
596 }
597 ok(got_subdir_change, "didn't get subdir change\n");
598
599 r = RemoveDirectoryW( subsubdir );
600 ok( r == TRUE, "failed to remove directory\n");
601
602 ov.Internal = 1;
603 ov.InternalHigh = 1;
604 r = ReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
605 ok(r==TRUE, "should return true\n");
606
607 r = RemoveDirectoryW( subdir );
608 ok( r == TRUE, "failed to remove directory\n");
609
610 r = WaitForSingleObject( ov.hEvent, 1000 );
611 ok( r == WAIT_OBJECT_0, "should be ready\n" );
612
613 pfni = (PFILE_NOTIFY_INFORMATION) buffer;
614 /* we may get a notification for the parent dir too */
615 if (pfni->Action == FILE_ACTION_MODIFIED && pfni->NextEntryOffset)
616 {
617 ok( pfni->FileNameLength == 3*sizeof(WCHAR), "len wrong %lu\n", pfni->FileNameLength );
618 ok( !memcmp(pfni->FileName,&szGa[1],3*sizeof(WCHAR)), "name wrong\n" );
619 pfni = (PFILE_NOTIFY_INFORMATION)((char *)pfni + pfni->NextEntryOffset);
620 }
621 ok( pfni->NextEntryOffset == 0, "offset wrong %lu\n", pfni->NextEntryOffset );
622 ok( pfni->Action == FILE_ACTION_REMOVED, "action wrong %lu\n", pfni->Action );
623 ok( pfni->FileNameLength == 6*sizeof(WCHAR), "len wrong %lu\n", pfni->FileNameLength );
624 ok( !memcmp(pfni->FileName,&szGa[1],6*sizeof(WCHAR)), "name wrong\n" );
625
626 ok( (NTSTATUS)ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
627 dwCount = (char *)&pfni->FileName[pfni->FileNameLength/sizeof(WCHAR)] - buffer;
628 ok( ov.InternalHigh == dwCount, "ov.InternalHigh wrong %Iu/%lu\n",ov.InternalHigh, dwCount );
629
630 CloseHandle(hdir);
631
632 r = RemoveDirectoryW( path );
633 ok( r == TRUE, "failed to remove directory\n");
634}
635
636/* show the behaviour when a null buffer is passed */
637static void test_readdirectorychanges_null(void)
638{
639 NTSTATUS r;
640 HANDLE hdir;
641 char buffer[0x1000];
642 DWORD fflags, filter = 0;
643 OVERLAPPED ov;
644 WCHAR path[MAX_PATH], subdir[MAX_PATH];
645 static const WCHAR szBoo[] = { '\\','b','o','o',0 };
646 static const WCHAR szHoo[] = { '\\','h','o','o',0 };
647 PFILE_NOTIFY_INFORMATION pfni;
648
649 r = GetTempPathW( MAX_PATH, path );
650 ok( r != 0, "temp path failed\n");
651
652 lstrcatW( path, szBoo );
653 lstrcpyW( subdir, path );
654 lstrcatW( subdir, szHoo );
655
656 RemoveDirectoryW( subdir );
657 RemoveDirectoryW( path );
658
659 r = CreateDirectoryW(path, NULL);
660 ok( r == TRUE, "failed to create directory\n");
661
662 fflags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
663 hdir = CreateFileW(path, GENERIC_READ|SYNCHRONIZE|FILE_LIST_DIRECTORY,
664 FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
665 OPEN_EXISTING, fflags, NULL);
666 ok( hdir != INVALID_HANDLE_VALUE, "failed to open directory\n");
667
668 ov.hEvent = CreateEventW( NULL, 1, 0, NULL );
669
670 filter = FILE_NOTIFY_CHANGE_FILE_NAME;
671 filter |= FILE_NOTIFY_CHANGE_DIR_NAME;
672
673 SetLastError(0xd0b00b00);
674 ov.Internal = 0;
675 ov.InternalHigh = 0;
676 memset( buffer, 0, sizeof buffer );
677
678 r = ReadDirectoryChangesW(hdir,NULL,0,FALSE,filter,NULL,&ov,NULL);
679 ok(r==TRUE, "should return true\n");
680
681 r = WaitForSingleObject( ov.hEvent, 0 );
682 ok( r == STATUS_TIMEOUT, "should timeout\n" );
683
684 r = CreateDirectoryW( subdir, NULL );
685 ok( r == TRUE, "failed to create directory\n");
686
687 r = WaitForSingleObject( ov.hEvent, 0 );
688 ok( r == WAIT_OBJECT_0, "event should be ready\n" );
689
690 ok( (NTSTATUS)ov.Internal == STATUS_NOTIFY_ENUM_DIR, "ov.Internal wrong\n");
691 ok( ov.InternalHigh == 0, "ov.InternalHigh wrong\n");
692
693 ov.Internal = 0;
694 ov.InternalHigh = 0;
695 ov.Offset = 0;
696 ov.OffsetHigh = 0;
697 memset( buffer, 0, sizeof buffer );
698
699 r = ReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
700 ok(r==TRUE, "should return true\n");
701
702 r = WaitForSingleObject( ov.hEvent, 0 );
703 ok( r == STATUS_TIMEOUT, "should timeout\n" );
704
705 r = RemoveDirectoryW( subdir );
706 ok( r == TRUE, "failed to remove directory\n");
707
708 r = WaitForSingleObject( ov.hEvent, 1000 );
709 ok( r == WAIT_OBJECT_0, "should be ready\n" );
710
711 ok( (NTSTATUS)ov.Internal == STATUS_NOTIFY_ENUM_DIR, "ov.Internal wrong\n");
712 ok( ov.InternalHigh == 0, "ov.InternalHigh wrong\n");
713
714 pfni = (PFILE_NOTIFY_INFORMATION) buffer;
715 ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
716
717 CloseHandle(hdir);
718
719 r = RemoveDirectoryW( path );
720 ok( r == TRUE, "failed to remove directory\n");
721}
722
723static void test_readdirectorychanges_filedir(void)
724{
725 NTSTATUS r;
726 HANDLE hdir, hfile;
727 char buffer[0x1000];
728 DWORD fflags, filter = 0;
729 OVERLAPPED ov;
730 WCHAR path[MAX_PATH], subdir[MAX_PATH], file[MAX_PATH];
731 static const WCHAR szBoo[] = { '\\','b','o','o',0 };
732 static const WCHAR szHoo[] = { '\\','h','o','o',0 };
733 static const WCHAR szFoo[] = { '\\','f','o','o',0 };
734 PFILE_NOTIFY_INFORMATION pfni;
735
736 r = GetTempPathW( MAX_PATH, path );
737 ok( r != 0, "temp path failed\n");
738
739 lstrcatW( path, szBoo );
740 lstrcpyW( subdir, path );
741 lstrcatW( subdir, szHoo );
742
743 lstrcpyW( file, path );
744 lstrcatW( file, szFoo );
745
746 DeleteFileW( file );
747 RemoveDirectoryW( subdir );
748 RemoveDirectoryW( path );
749
750 r = CreateDirectoryW(path, NULL);
751 ok( r == TRUE, "failed to create directory\n");
752
753 fflags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
754 hdir = CreateFileW(path, GENERIC_READ|SYNCHRONIZE|FILE_LIST_DIRECTORY,
755 FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
756 OPEN_EXISTING, fflags, NULL);
757 ok( hdir != INVALID_HANDLE_VALUE, "failed to open directory\n");
758
759 ov.hEvent = CreateEventW( NULL, 0, 0, NULL );
760
761 filter = FILE_NOTIFY_CHANGE_FILE_NAME;
762
763 r = ReadDirectoryChangesW(hdir,buffer,sizeof buffer,TRUE,filter,NULL,&ov,NULL);
764 ok(r==TRUE, "should return true\n");
765
766 r = WaitForSingleObject( ov.hEvent, 10 );
767 ok( r == WAIT_TIMEOUT, "should timeout\n" );
768
769 r = CreateDirectoryW( subdir, NULL );
770 ok( r == TRUE, "failed to create directory\n");
771
772 hfile = CreateFileW( file, GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL );
773 ok( hfile != INVALID_HANDLE_VALUE, "failed to create file\n");
774 ok( CloseHandle(hfile), "failed to close file\n");
775
776 r = WaitForSingleObject( ov.hEvent, 1000 );
777 ok( r == WAIT_OBJECT_0, "event should be ready\n" );
778
779 ok( (NTSTATUS)ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
780 ok( ov.InternalHigh == 0x12, "ov.InternalHigh wrong\n");
781
782 pfni = (PFILE_NOTIFY_INFORMATION) buffer;
783 ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
784 ok( pfni->Action == FILE_ACTION_ADDED, "action wrong\n" );
785 ok( pfni->FileNameLength == 6, "len wrong\n" );
786 ok( !memcmp(pfni->FileName,&szFoo[1],6), "name wrong\n" );
787
788 r = DeleteFileW( file );
789 ok( r == TRUE, "failed to delete file\n");
790
791 r = RemoveDirectoryW( subdir );
792 ok( r == TRUE, "failed to remove directory\n");
793
794 CloseHandle(hdir);
795
796 r = RemoveDirectoryW( path );
797 ok( r == TRUE, "failed to remove directory\n");
798}
799
800static void CALLBACK readdirectorychanges_cr(DWORD error, DWORD len, LPOVERLAPPED ov)
801{
802 ok(error == 0, "ReadDirectoryChangesW error %ld\n", error);
803 ok(ov->hEvent == (void*)0xdeadbeef, "hEvent should not have changed\n");
804}
805
806static void test_readdirectorychanges_cr(void)
807{
808 static const WCHAR szBoo[] = { '\\','b','o','o','\\',0 };
809 static const WCHAR szDir[] = { 'd','i','r',0 };
810 static const WCHAR szFile[] = { 'f','i','l','e',0 };
811 static const WCHAR szBackslash[] = { '\\',0 };
812
813 WCHAR path[MAX_PATH], file[MAX_PATH], dir[MAX_PATH], sub_file[MAX_PATH];
814 FILE_NOTIFY_INFORMATION fni[1024], *fni_next;
815 OVERLAPPED ov;
816 HANDLE hdir, hfile;
817 NTSTATUS r;
818
819 r = GetTempPathW(MAX_PATH, path);
820 ok(r != 0, "temp path failed\n");
821
822 lstrcatW(path, szBoo);
823 lstrcpyW(dir, path);
824 lstrcatW(dir, szDir);
825 lstrcpyW(file, path);
826 lstrcatW(file, szFile);
827 lstrcpyW(sub_file, dir);
828 lstrcatW(sub_file, szBackslash);
829 lstrcatW(sub_file, szFile);
830
831 DeleteFileW(file);
832 RemoveDirectoryW(dir);
833 RemoveDirectoryW(path);
834
835 r = CreateDirectoryW(path, NULL);
836 ok(r == TRUE, "failed to create directory\n");
837
838 hdir = CreateFileW(path, GENERIC_READ,
839 FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
840 FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
841 ok(hdir != INVALID_HANDLE_VALUE, "failed to open directory\n");
842
843 memset(&ov, 0, sizeof(ov));
844 ov.hEvent = (void*)0xdeadbeef;
845 r = ReadDirectoryChangesW(hdir, fni, sizeof(fni), FALSE,
846 FILE_NOTIFY_CHANGE_FILE_NAME, NULL, &ov, readdirectorychanges_cr);
847 ok(r == TRUE, "ReadDirectoryChangesW failed\n");
848
849 hfile = CreateFileW(file, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
850 ok(hfile != INVALID_HANDLE_VALUE, "failed to create file\n");
851 CloseHandle(hfile);
852
853 r = SleepEx(1000, TRUE);
854 ok(r != 0, "failed to receive file creation event\n");
855 ok(fni->NextEntryOffset == 0, "there should be no more events in buffer\n");
856 ok(fni->Action == FILE_ACTION_ADDED, "Action = %ld\n", fni->Action);
857 ok(fni->FileNameLength == lstrlenW(szFile)*sizeof(WCHAR),
858 "FileNameLength = %ld\n", fni->FileNameLength);
859 ok(!memcmp(fni->FileName, szFile, lstrlenW(szFile)*sizeof(WCHAR)),
860 "FileName = %s\n", wine_dbgstr_wn(fni->FileName, fni->FileNameLength/sizeof(WCHAR)));
861
862 r = ReadDirectoryChangesW(hdir, fni, sizeof(fni), FALSE,
863 FILE_NOTIFY_CHANGE_FILE_NAME, NULL, &ov, readdirectorychanges_cr);
864 ok(r == TRUE, "ReadDirectoryChangesW failed\n");
865
866 /* This event will not be reported */
867 r = CreateDirectoryW(dir, NULL);
868 ok(r == TRUE, "failed to create directory\n");
869
870 r = MoveFileW(file, sub_file);
871 ok(r == TRUE, "failed to move file\n");
872
873 r = SleepEx(1000, TRUE);
874 ok(r != 0, "failed to receive file move event\n");
875 ok(fni->NextEntryOffset == 0, "there should be no more events in buffer\n");
876 ok(fni->Action == FILE_ACTION_REMOVED, "Action = %ld\n", fni->Action);
877 ok(fni->FileNameLength == lstrlenW(szFile)*sizeof(WCHAR),
878 "FileNameLength = %ld\n", fni->FileNameLength);
879 ok(!memcmp(fni->FileName, szFile, lstrlenW(szFile)*sizeof(WCHAR)),
880 "FileName = %s\n", wine_dbgstr_wn(fni->FileName, fni->FileNameLength/sizeof(WCHAR)));
881
882 r = ReadDirectoryChangesW(hdir, fni, sizeof(fni), FALSE,
883 FILE_NOTIFY_CHANGE_FILE_NAME, NULL, &ov, readdirectorychanges_cr);
884 ok(r == TRUE, "ReadDirectoryChangesW failed\n");
885
886 r = MoveFileW(sub_file, file);
887 ok(r == TRUE, "failed to move file\n");
888
889 r = SleepEx(1000, TRUE);
890 ok(r != 0, "failed to receive file move event\n");
891 ok(fni->NextEntryOffset == 0, "there should be no more events in buffer\n");
892 ok(fni->Action == FILE_ACTION_ADDED, "Action = %ld\n", fni->Action);
893 ok(fni->FileNameLength == lstrlenW(szFile)*sizeof(WCHAR),
894 "FileNameLength = %ld\n", fni->FileNameLength);
895 ok(!memcmp(fni->FileName, szFile, lstrlenW(szFile)*sizeof(WCHAR)),
896 "FileName = %s\n", wine_dbgstr_wn(fni->FileName, fni->FileNameLength/sizeof(WCHAR)));
897
898 r = ReadDirectoryChangesW(hdir, fni, sizeof(fni), FALSE,
899 FILE_NOTIFY_CHANGE_FILE_NAME, NULL, &ov, readdirectorychanges_cr);
900 ok(r == TRUE, "ReadDirectoryChangesW failed\n");
901
902 r = DeleteFileW(file);
903 ok(r == TRUE, "failed to delete file\n");
904
905 r = SleepEx(1000, TRUE);
906 ok(r != 0, "failed to receive file removal event\n");
907 ok(fni->NextEntryOffset == 0, "there should be no more events in buffer\n");
908 ok(fni->Action == FILE_ACTION_REMOVED, "Action = %ld\n", fni->Action);
909 ok(fni->FileNameLength == lstrlenW(szFile)*sizeof(WCHAR),
910 "FileNameLength = %ld\n", fni->FileNameLength);
911 ok(!memcmp(fni->FileName, szFile, lstrlenW(szFile)*sizeof(WCHAR)),
912 "FileName = %s\n", wine_dbgstr_wn(fni->FileName, fni->FileNameLength/sizeof(WCHAR)));
913
914 CloseHandle(hdir);
915
916 hdir = CreateFileW(path, GENERIC_READ,
917 FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
918 FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
919 ok(hdir != INVALID_HANDLE_VALUE, "failed to open directory\n");
920
921 r = ReadDirectoryChangesW(hdir, fni, sizeof(fni), FALSE,
922 FILE_NOTIFY_CHANGE_DIR_NAME, NULL, &ov, readdirectorychanges_cr);
923 ok(r == TRUE, "ReadDirectoryChangesW failed\n");
924
925 r = MoveFileW(dir, file);
926 ok(r == TRUE, "failed to move directory\n");
927
928 r = SleepEx(1000, TRUE);
929 ok(r != 0, "failed to receive directory move event\n");
930 if (fni->Action == FILE_ACTION_RENAMED_OLD_NAME)
931 {
932 ok(fni->Action == FILE_ACTION_RENAMED_OLD_NAME, "Action = %ld\n", fni->Action);
933 ok(fni->FileNameLength == lstrlenW(szDir)*sizeof(WCHAR),
934 "FileNameLength = %ld\n", fni->FileNameLength);
935 ok(!memcmp(fni->FileName, szDir, lstrlenW(szDir)*sizeof(WCHAR)),
936 "FileName = %s\n", wine_dbgstr_wn(fni->FileName, fni->FileNameLength/sizeof(WCHAR)));
937 ok(fni->NextEntryOffset != 0, "no next entry in movement event\n");
938 fni_next = (FILE_NOTIFY_INFORMATION*)((char*)fni+fni->NextEntryOffset);
939 ok(fni_next->NextEntryOffset == 0, "there should be no more events in buffer\n");
940 ok(fni_next->Action == FILE_ACTION_RENAMED_NEW_NAME, "Action = %ld\n", fni_next->Action);
941 ok(fni_next->FileNameLength == lstrlenW(szFile)*sizeof(WCHAR),
942 "FileNameLength = %ld\n", fni_next->FileNameLength);
943 ok(!memcmp(fni_next->FileName, szFile, lstrlenW(szFile)*sizeof(WCHAR)),
944 "FileName = %s\n", wine_dbgstr_wn(fni_next->FileName, fni_next->FileNameLength/sizeof(WCHAR)));
945 }
946 else
947 {
948 todo_wine ok(0, "Expected rename event\n");
949
950 if (fni->NextEntryOffset == 0)
951 {
952 r = ReadDirectoryChangesW(hdir, fni, sizeof(fni), FALSE,
953 FILE_NOTIFY_CHANGE_DIR_NAME, NULL, &ov, readdirectorychanges_cr);
954 ok(r == TRUE, "ReadDirectoryChangesW failed\n");
955
956 r = SleepEx(1000, TRUE);
957 ok(r != 0, "failed to receive directory move event\n");
958 }
959 }
960
961 r = CreateDirectoryW(dir, NULL);
962 ok(r == TRUE, "failed to create directory\n");
963
964 r = RemoveDirectoryW(dir);
965 ok(r == TRUE, "failed to remove directory\n");
966
967 r = ReadDirectoryChangesW(hdir, fni, sizeof(fni), FALSE,
968 FILE_NOTIFY_CHANGE_DIR_NAME, NULL, &ov, readdirectorychanges_cr);
969 ok(r == TRUE, "ReadDirectoryChangesW failed\n");
970
971 r = SleepEx(1000, TRUE);
972 ok(r != 0, "failed to receive directory creation event\n");
973 ok(fni->Action == FILE_ACTION_ADDED, "Action = %ld\n", fni->Action);
974 ok(fni->FileNameLength == lstrlenW(szDir)*sizeof(WCHAR),
975 "FileNameLength = %ld\n", fni->FileNameLength);
976 ok(!memcmp(fni->FileName, szDir, lstrlenW(szDir)*sizeof(WCHAR)),
977 "FileName = %s\n", wine_dbgstr_wn(fni->FileName, fni->FileNameLength/sizeof(WCHAR)));
978 if (fni->NextEntryOffset)
979 fni_next = (FILE_NOTIFY_INFORMATION*)((char*)fni+fni->NextEntryOffset);
980 else
981 {
982 r = ReadDirectoryChangesW(hdir, fni, sizeof(fni), FALSE,
983 FILE_NOTIFY_CHANGE_DIR_NAME, NULL, &ov, readdirectorychanges_cr);
984 ok(r == TRUE, "ReadDirectoryChangesW failed\n");
985
986 r = SleepEx(1000, TRUE);
987 ok(r != 0, "failed to receive directory removal event\n");
988 fni_next = fni;
989 }
990 ok(fni_next->NextEntryOffset == 0, "there should be no more events in buffer\n");
991 ok(fni_next->Action == FILE_ACTION_REMOVED, "Action = %ld\n", fni_next->Action);
992 ok(fni_next->FileNameLength == lstrlenW(szDir)*sizeof(WCHAR),
993 "FileNameLength = %ld\n", fni_next->FileNameLength);
994 ok(!memcmp(fni_next->FileName, szDir, lstrlenW(szDir)*sizeof(WCHAR)),
995 "FileName = %s\n", wine_dbgstr_wn(fni_next->FileName, fni_next->FileNameLength/sizeof(WCHAR)));
996
997 CloseHandle(hdir);
998 RemoveDirectoryW(file);
999 RemoveDirectoryW(path);
1000}
1001
1002static void test_ffcn_directory_overlap(void)
1003{
1004 HANDLE parent_watch, child_watch, parent_thread, child_thread;
1005 char workdir[MAX_PATH], parentdir[MAX_PATH], childdir[MAX_PATH];
1006 char tempfile[MAX_PATH];
1007 DWORD threadId;
1008 BOOL ret;
1009
1010 /* Setup directory hierarchy */
1011 ret = GetTempPathA(MAX_PATH, workdir);
1012 ok((ret > 0) && (ret <= MAX_PATH),
1013 "GetTempPathA error: %ld\n", GetLastError());
1014
1015 ret = GetTempFileNameA(workdir, "fcn", 0, tempfile);
1016 ok(ret, "GetTempFileNameA error: %ld\n", GetLastError());
1017 ret = DeleteFileA(tempfile);
1018 ok(ret, "DeleteFileA error: %ld\n", GetLastError());
1019
1020 lstrcpyA(parentdir, tempfile);
1021 ret = CreateDirectoryA(parentdir, NULL);
1022 ok(ret, "CreateDirectoryA error: %ld\n", GetLastError());
1023
1024 lstrcpyA(childdir, parentdir);
1025 lstrcatA(childdir, "\\c");
1026 ret = CreateDirectoryA(childdir, NULL);
1027 ok(ret, "CreateDirectoryA error: %ld\n", GetLastError());
1028
1029
1030 /* When recursively watching overlapping directories, changes in child
1031 * should trigger notifications for both child and parent */
1032 parent_thread = StartNotificationThread(parentdir, TRUE,
1033 FILE_NOTIFY_CHANGE_FILE_NAME);
1034 child_thread = StartNotificationThread(childdir, TRUE,
1035 FILE_NOTIFY_CHANGE_FILE_NAME);
1036
1037 /* Create a file in child */
1038 ret = GetTempFileNameA(childdir, "fcn", 0, tempfile);
1039 ok(ret, "GetTempFileNameA error: %ld\n", GetLastError());
1040
1041 /* Both watches should trigger */
1042 ret = FinishNotificationThread(parent_thread);
1043 ok(ret, "Missed parent notification\n");
1044 ret = FinishNotificationThread(child_thread);
1045 ok(ret, "Missed child notification\n");
1046
1047 ret = DeleteFileA(tempfile);
1048 ok(ret, "DeleteFileA error: %ld\n", GetLastError());
1049
1050
1051 /* Removing a recursive parent watch should not affect child watches. Doing
1052 * so used to crash wineserver. */
1053 parent_watch = FindFirstChangeNotificationA(parentdir, TRUE,
1054 FILE_NOTIFY_CHANGE_FILE_NAME);
1055 ok(parent_watch != INVALID_HANDLE_VALUE,
1056 "FindFirstChangeNotification error: %ld\n", GetLastError());
1057 child_watch = FindFirstChangeNotificationA(childdir, TRUE,
1058 FILE_NOTIFY_CHANGE_FILE_NAME);
1059 ok(child_watch != INVALID_HANDLE_VALUE,
1060 "FindFirstChangeNotification error: %ld\n", GetLastError());
1061
1062 ret = FindCloseChangeNotification(parent_watch);
1063 ok(ret, "FindCloseChangeNotification error: %ld\n", GetLastError());
1064
1065 child_thread = CreateThread(NULL, 0, NotificationThread, child_watch, 0,
1066 &threadId);
1067 ok(child_thread != NULL, "CreateThread error: %ld\n", GetLastError());
1068
1069 /* Create a file in child */
1070 ret = GetTempFileNameA(childdir, "fcn", 0, tempfile);
1071 ok(ret, "GetTempFileNameA error: %ld\n", GetLastError());
1072
1073 /* Child watch should trigger */
1074 ret = FinishNotificationThread(child_thread);
1075 ok(ret, "Missed child notification\n");
1076
1077 /* clean up */
1078 ret = DeleteFileA(tempfile);
1079 ok(ret, "DeleteFileA error: %ld\n", GetLastError());
1080
1081 ret = RemoveDirectoryA(childdir);
1082 ok(ret, "RemoveDirectoryA error: %ld\n", GetLastError());
1083
1084 ret = RemoveDirectoryA(parentdir);
1085 ok(ret, "RemoveDirectoryA error: %ld\n", GetLastError());
1086}
1087
1088START_TEST(change)
1089{
1090 test_ffcnMultipleThreads();
1091 /* The above function runs a test that must occur before FindCloseChangeNotification is run in the
1092 current thread to preserve the emptiness of the wine user APC queue. To ensure this it should be
1093 placed first. */
1094 test_FindFirstChangeNotification();
1095 test_ffcn();
1096 test_readdirectorychanges();
1097 test_readdirectorychanges_null();
1098 test_readdirectorychanges_filedir();
1099 test_readdirectorychanges_cr();
1100 test_ffcn_directory_overlap();
1101}