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