Merge trunk HEAD (r46369)
[reactos.git] / rostests / winetests / kernel32 / 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 = 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, change, 0, &threadId);
69 ok(thread != NULL, "CreateThread error: %d\n", GetLastError());
70
71 return thread;
72 }
73
74 static DWORD FinishNotificationThread(HANDLE thread)
75 {
76 DWORD status, exitcode;
77
78 status = WaitForSingleObject(thread, 5000);
79 ok(status == WAIT_OBJECT_0, "WaitForSingleObject status %d error %d\n", status, GetLastError());
80
81 ok(GetExitCodeThread(thread, &exitcode), "Could not retrieve thread exit code\n");
82
83 return exitcode;
84 }
85
86 static void test_FindFirstChangeNotification(void)
87 {
88 HANDLE change, file, thread;
89 DWORD attributes, count;
90 BOOL ret;
91
92 char workdir[MAX_PATH], dirname1[MAX_PATH], dirname2[MAX_PATH];
93 char filename1[MAX_PATH], filename2[MAX_PATH];
94 static const char prefix[] = "FCN";
95 char buffer[2048];
96
97 /* pathetic checks */
98
99 change = FindFirstChangeNotificationA("not-a-file", FALSE, FILE_NOTIFY_CHANGE_FILE_NAME);
100 ok(change == INVALID_HANDLE_VALUE, "Expected INVALID_HANDLE_VALUE, got %p\n", change);
101 ok(GetLastError() == ERROR_FILE_NOT_FOUND ||
102 GetLastError() == ERROR_NO_MORE_FILES, /* win95 */
103 "FindFirstChangeNotification error: %d\n", GetLastError());
104
105 if (0) /* This documents win2k behavior. It crashes on win98. */
106 {
107 change = FindFirstChangeNotificationA(NULL, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME);
108 ok(change == NULL && GetLastError() == ERROR_PATH_NOT_FOUND,
109 "FindFirstChangeNotification error: %d\n", GetLastError());
110 }
111
112 ret = FindNextChangeNotification(NULL);
113 ok(!ret && GetLastError() == ERROR_INVALID_HANDLE, "FindNextChangeNotification error: %d\n",
114 GetLastError());
115
116 ret = FindCloseChangeNotification(NULL);
117 ok(!ret && GetLastError() == ERROR_INVALID_HANDLE, "FindCloseChangeNotification error: %d\n",
118 GetLastError());
119
120 ret = GetTempPathA(MAX_PATH, workdir);
121 ok(ret, "GetTempPathA error: %d\n", GetLastError());
122
123 lstrcatA(workdir, "testFileChangeNotification");
124
125 ret = CreateDirectoryA(workdir, NULL);
126 ok(ret, "CreateDirectoryA error: %d\n", GetLastError());
127
128 ret = GetTempFileNameA(workdir, prefix, 0, filename1);
129 ok(ret, "GetTempFileNameA error: %d\n", GetLastError());
130
131 file = CreateFileA(filename1, GENERIC_WRITE|GENERIC_READ, 0, NULL, CREATE_ALWAYS,
132 FILE_ATTRIBUTE_NORMAL, 0);
133 ok(file != INVALID_HANDLE_VALUE, "CreateFileA error: %d\n", GetLastError());
134 ret = CloseHandle(file);
135 ok( ret, "CloseHandle error: %d\n", GetLastError());
136
137 /* Try to register notification for a file. win98 and win2k behave differently here */
138 change = FindFirstChangeNotificationA(filename1, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME);
139 ok(change == INVALID_HANDLE_VALUE && (GetLastError() == ERROR_DIRECTORY ||
140 GetLastError() == ERROR_FILE_NOT_FOUND),
141 "FindFirstChangeNotification error: %d\n", GetLastError());
142
143 lstrcpyA(dirname1, filename1);
144 lstrcatA(dirname1, "dir");
145
146 lstrcpyA(dirname2, dirname1);
147 lstrcatA(dirname2, "new");
148
149 ret = CreateDirectoryA(dirname1, NULL);
150 ok(ret, "CreateDirectoryA error: %d\n", GetLastError());
151
152 /* What if we move the directory we registered notification for? */
153 thread = StartNotificationThread(dirname1, FALSE, FILE_NOTIFY_CHANGE_DIR_NAME);
154 ret = MoveFileA(dirname1, dirname2);
155 ok(ret, "MoveFileA error: %d\n", GetLastError());
156 /* win9x and win2k behave differently here, don't check result */
157 FinishNotificationThread(thread);
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: %d\n", GetLastError());
163 /* win9x and win2k behave differently here, don't check result */
164 FinishNotificationThread(thread);
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: %d\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: %d\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: %d\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: %d\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: %d\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: %d\n", GetLastError());
206 ret = CloseHandle(file);
207 ok( ret, "CloseHandle error: %d\n", GetLastError());
208 ok(FinishNotificationThread(thread), "Missed notification\n");
209
210 attributes = GetFileAttributesA(filename2);
211 ok(attributes != INVALID_FILE_ATTRIBUTES, "GetFileAttributesA error: %d\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: %d\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: %d\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: %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 win_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, handles[0], 0,
359 &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 win_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 win_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 == 6*sizeof(WCHAR), "len wrong\n" );
563 ok( !memcmp(pfni->FileName,&szGa[1],6*sizeof(WCHAR)), "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 /* we may get a notification for the parent dir too */
581 if (pfni->Action == FILE_ACTION_MODIFIED && pfni->NextEntryOffset)
582 {
583 ok( pfni->FileNameLength == 3*sizeof(WCHAR), "len wrong %u\n", pfni->FileNameLength );
584 ok( !memcmp(pfni->FileName,&szGa[1],3*sizeof(WCHAR)), "name wrong\n" );
585 pfni = (PFILE_NOTIFY_INFORMATION)((char *)pfni + pfni->NextEntryOffset);
586 }
587 ok( pfni->NextEntryOffset == 0, "offset wrong %u\n", pfni->NextEntryOffset );
588 ok( pfni->Action == FILE_ACTION_REMOVED, "action wrong %u\n", pfni->Action );
589 ok( pfni->FileNameLength == 6*sizeof(WCHAR), "len wrong %u\n", pfni->FileNameLength );
590 ok( !memcmp(pfni->FileName,&szGa[1],6*sizeof(WCHAR)), "name wrong\n" );
591
592 ok( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
593 dwCount = (char *)&pfni->FileName[pfni->FileNameLength/sizeof(WCHAR)] - buffer;
594 ok( ov.InternalHigh == dwCount, "ov.InternalHigh wrong %lu/%u\n",ov.InternalHigh, dwCount );
595
596 CloseHandle(hdir);
597
598 r = RemoveDirectoryW( path );
599 ok( r == TRUE, "failed to remove directory\n");
600 }
601
602 /* show the behaviour when a null buffer is passed */
603 static void test_readdirectorychanges_null(void)
604 {
605 NTSTATUS r;
606 HANDLE hdir;
607 char buffer[0x1000];
608 DWORD fflags, filter = 0;
609 OVERLAPPED ov;
610 WCHAR path[MAX_PATH], subdir[MAX_PATH];
611 static const WCHAR szBoo[] = { '\\','b','o','o',0 };
612 static const WCHAR szHoo[] = { '\\','h','o','o',0 };
613 PFILE_NOTIFY_INFORMATION pfni;
614
615 if (!pReadDirectoryChangesW)
616 {
617 win_skip("ReadDirectoryChangesW is not available\n");
618 return;
619 }
620 SetLastError(0xdeadbeef);
621 r = GetTempPathW( MAX_PATH, path );
622 if (!r && (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED))
623 {
624 win_skip("GetTempPathW is not implemented\n");
625 return;
626 }
627 ok( r != 0, "temp path failed\n");
628 if (!r)
629 return;
630
631 lstrcatW( path, szBoo );
632 lstrcpyW( subdir, path );
633 lstrcatW( subdir, szHoo );
634
635 RemoveDirectoryW( subdir );
636 RemoveDirectoryW( path );
637
638 r = CreateDirectoryW(path, NULL);
639 ok( r == TRUE, "failed to create directory\n");
640
641 fflags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
642 hdir = CreateFileW(path, GENERIC_READ|SYNCHRONIZE|FILE_LIST_DIRECTORY,
643 FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
644 OPEN_EXISTING, fflags, NULL);
645 ok( hdir != INVALID_HANDLE_VALUE, "failed to open directory\n");
646
647 ov.hEvent = CreateEvent( NULL, 1, 0, NULL );
648
649 filter = FILE_NOTIFY_CHANGE_FILE_NAME;
650 filter |= FILE_NOTIFY_CHANGE_DIR_NAME;
651
652 SetLastError(0xd0b00b00);
653 ov.Internal = 0;
654 ov.InternalHigh = 0;
655 memset( buffer, 0, sizeof buffer );
656
657 r = pReadDirectoryChangesW(hdir,NULL,0,FALSE,filter,NULL,&ov,NULL);
658 ok(r==TRUE, "should return true\n");
659
660 r = WaitForSingleObject( ov.hEvent, 0 );
661 ok( r == STATUS_TIMEOUT, "should timeout\n" );
662
663 r = CreateDirectoryW( subdir, NULL );
664 ok( r == TRUE, "failed to create directory\n");
665
666 r = WaitForSingleObject( ov.hEvent, 0 );
667 ok( r == WAIT_OBJECT_0, "event should be ready\n" );
668
669 ok( ov.Internal == STATUS_NOTIFY_ENUM_DIR, "ov.Internal wrong\n");
670 ok( ov.InternalHigh == 0, "ov.InternalHigh wrong\n");
671
672 ov.Internal = 0;
673 ov.InternalHigh = 0;
674 S(U(ov)).Offset = 0;
675 S(U(ov)).OffsetHigh = 0;
676 memset( buffer, 0, sizeof buffer );
677
678 r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,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 = RemoveDirectoryW( subdir );
685 ok( r == TRUE, "failed to remove directory\n");
686
687 r = WaitForSingleObject( ov.hEvent, 1000 );
688 ok( r == WAIT_OBJECT_0, "should be ready\n" );
689
690 ok( ov.Internal == STATUS_NOTIFY_ENUM_DIR, "ov.Internal wrong\n");
691 ok( ov.InternalHigh == 0, "ov.InternalHigh wrong\n");
692
693 pfni = (PFILE_NOTIFY_INFORMATION) buffer;
694 ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
695
696 CloseHandle(hdir);
697
698 r = RemoveDirectoryW( path );
699 ok( r == TRUE, "failed to remove directory\n");
700 }
701
702 static void test_readdirectorychanges_filedir(void)
703 {
704 NTSTATUS r;
705 HANDLE hdir, hfile;
706 char buffer[0x1000];
707 DWORD fflags, filter = 0;
708 OVERLAPPED ov;
709 WCHAR path[MAX_PATH], subdir[MAX_PATH], file[MAX_PATH];
710 static const WCHAR szBoo[] = { '\\','b','o','o',0 };
711 static const WCHAR szHoo[] = { '\\','h','o','o',0 };
712 static const WCHAR szFoo[] = { '\\','f','o','o',0 };
713 PFILE_NOTIFY_INFORMATION pfni;
714
715 SetLastError(0xdeadbeef);
716 r = GetTempPathW( MAX_PATH, path );
717 if (!r && (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED))
718 {
719 win_skip("GetTempPathW is not implemented\n");
720 return;
721 }
722 ok( r != 0, "temp path failed\n");
723 if (!r)
724 return;
725
726 lstrcatW( path, szBoo );
727 lstrcpyW( subdir, path );
728 lstrcatW( subdir, szHoo );
729
730 lstrcpyW( file, path );
731 lstrcatW( file, szFoo );
732
733 DeleteFileW( file );
734 RemoveDirectoryW( subdir );
735 RemoveDirectoryW( path );
736
737 r = CreateDirectoryW(path, NULL);
738 ok( r == TRUE, "failed to create directory\n");
739
740 fflags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
741 hdir = CreateFileW(path, GENERIC_READ|SYNCHRONIZE|FILE_LIST_DIRECTORY,
742 FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
743 OPEN_EXISTING, fflags, NULL);
744 ok( hdir != INVALID_HANDLE_VALUE, "failed to open directory\n");
745
746 ov.hEvent = CreateEvent( NULL, 0, 0, NULL );
747
748 filter = FILE_NOTIFY_CHANGE_FILE_NAME;
749
750 r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,TRUE,filter,NULL,&ov,NULL);
751 ok(r==TRUE, "should return true\n");
752
753 r = WaitForSingleObject( ov.hEvent, 10 );
754 ok( r == WAIT_TIMEOUT, "should timeout\n" );
755
756 r = CreateDirectoryW( subdir, NULL );
757 ok( r == TRUE, "failed to create directory\n");
758
759 hfile = CreateFileW( file, GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL );
760 ok( hfile != INVALID_HANDLE_VALUE, "failed to create file\n");
761 ok( CloseHandle(hfile), "failed toc lose file\n");
762
763 r = WaitForSingleObject( ov.hEvent, 1000 );
764 ok( r == WAIT_OBJECT_0, "event should be ready\n" );
765
766 ok( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
767 ok( ov.InternalHigh == 0x12, "ov.InternalHigh wrong\n");
768
769 pfni = (PFILE_NOTIFY_INFORMATION) buffer;
770 ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
771 ok( pfni->Action == FILE_ACTION_ADDED, "action wrong\n" );
772 ok( pfni->FileNameLength == 6, "len wrong\n" );
773 ok( !memcmp(pfni->FileName,&szFoo[1],6), "name wrong\n" );
774
775 r = DeleteFileW( file );
776 ok( r == TRUE, "failed to delete file\n");
777
778 r = RemoveDirectoryW( subdir );
779 ok( r == TRUE, "failed to remove directory\n");
780
781 CloseHandle(hdir);
782
783 r = RemoveDirectoryW( path );
784 ok( r == TRUE, "failed to remove directory\n");
785 }
786
787 static void test_ffcn_directory_overlap(void)
788 {
789 HANDLE parent_watch, child_watch, parent_thread, child_thread;
790 char workdir[MAX_PATH], parentdir[MAX_PATH], childdir[MAX_PATH];
791 char tempfile[MAX_PATH];
792 DWORD threadId;
793 BOOL ret;
794
795 /* Setup directory hierarchy */
796 ret = GetTempPathA(MAX_PATH, workdir);
797 ok((ret > 0) && (ret <= MAX_PATH),
798 "GetTempPathA error: %d\n", GetLastError());
799
800 ret = GetTempFileNameA(workdir, "fcn", 0, tempfile);
801 ok(ret, "GetTempFileNameA error: %d\n", GetLastError());
802 ret = DeleteFileA(tempfile);
803 ok(ret, "DeleteFileA error: %d\n", GetLastError());
804
805 lstrcpyA(parentdir, tempfile);
806 ret = CreateDirectoryA(parentdir, NULL);
807 ok(ret, "CreateDirectoryA error: %d\n", GetLastError());
808
809 lstrcpyA(childdir, parentdir);
810 lstrcatA(childdir, "\\c");
811 ret = CreateDirectoryA(childdir, NULL);
812 ok(ret, "CreateDirectoryA error: %d\n", GetLastError());
813
814
815 /* When recursively watching overlapping directories, changes in child
816 * should trigger notifications for both child and parent */
817 parent_thread = StartNotificationThread(parentdir, TRUE,
818 FILE_NOTIFY_CHANGE_FILE_NAME);
819 child_thread = StartNotificationThread(childdir, TRUE,
820 FILE_NOTIFY_CHANGE_FILE_NAME);
821
822 /* Create a file in child */
823 ret = GetTempFileNameA(childdir, "fcn", 0, tempfile);
824 ok(ret, "GetTempFileNameA error: %d\n", GetLastError());
825
826 /* Both watches should trigger */
827 ret = FinishNotificationThread(parent_thread);
828 ok(ret, "Missed parent notification\n");
829 ret = FinishNotificationThread(child_thread);
830 ok(ret, "Missed child notification\n");
831
832 ret = DeleteFileA(tempfile);
833 ok(ret, "DeleteFileA error: %d\n", GetLastError());
834
835
836 /* Removing a recursive parent watch should not affect child watches. Doing
837 * so used to crash wineserver. */
838 parent_watch = FindFirstChangeNotificationA(parentdir, TRUE,
839 FILE_NOTIFY_CHANGE_FILE_NAME);
840 ok(parent_watch != INVALID_HANDLE_VALUE,
841 "FindFirstChangeNotification error: %d\n", GetLastError());
842 child_watch = FindFirstChangeNotificationA(childdir, TRUE,
843 FILE_NOTIFY_CHANGE_FILE_NAME);
844 ok(child_watch != INVALID_HANDLE_VALUE,
845 "FindFirstChangeNotification error: %d\n", GetLastError());
846
847 ret = FindCloseChangeNotification(parent_watch);
848 ok(ret, "FindCloseChangeNotification error: %d\n", GetLastError());
849
850 child_thread = CreateThread(NULL, 0, NotificationThread, child_watch, 0,
851 &threadId);
852 ok(child_thread != NULL, "CreateThread error: %d\n", GetLastError());
853
854 /* Create a file in child */
855 ret = GetTempFileNameA(childdir, "fcn", 0, tempfile);
856 ok(ret, "GetTempFileNameA error: %d\n", GetLastError());
857
858 /* Child watch should trigger */
859 ret = FinishNotificationThread(child_thread);
860 ok(ret, "Missed child notification\n");
861
862 /* clean up */
863 ret = DeleteFileA(tempfile);
864 ok(ret, "DeleteFileA error: %d\n", GetLastError());
865
866 ret = RemoveDirectoryA(childdir);
867 ok(ret, "RemoveDirectoryA error: %d\n", GetLastError());
868
869 ret = RemoveDirectoryA(parentdir);
870 ok(ret, "RemoveDirectoryA error: %d\n", GetLastError());
871 }
872
873 START_TEST(change)
874 {
875 HMODULE hkernel32 = GetModuleHandle("kernel32");
876 pReadDirectoryChangesW = (fnReadDirectoryChangesW)
877 GetProcAddress(hkernel32, "ReadDirectoryChangesW");
878
879 test_ffcnMultipleThreads();
880 /* The above function runs a test that must occur before FindCloseChangeNotification is run in the
881 current thread to preserve the emptiness of the wine user APC queue. To ensure this it should be
882 placed first. */
883 test_FindFirstChangeNotification();
884 test_ffcn();
885 test_readdirectorychanges();
886 test_readdirectorychanges_null();
887 test_readdirectorychanges_filedir();
888 test_ffcn_directory_overlap();
889 }