2 * Tests for file change notification functions
4 * Copyright (c) 2004 Hans Leidekker
5 * Copyright 2006 Mike McCormack for CodeWeavers
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.
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.
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
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
33 #define WIN32_NO_STATUS
34 #include "wine/test.h"
39 static DWORD CALLBACK NotificationThread(LPVOID arg)
42 BOOL notified = FALSE;
46 status = WaitForSingleObject(change, 100);
48 if (status == WAIT_OBJECT_0 ) {
50 ret = FindNextChangeNotification(change);
53 ret = FindCloseChangeNotification(change);
54 ok( ret, "FindCloseChangeNotification error: %d\n",
57 ExitThread((DWORD)notified);
60 static HANDLE StartNotificationThread(LPCSTR path, BOOL subtree, DWORD flags)
62 HANDLE change, thread;
65 change = FindFirstChangeNotificationA(path, subtree, flags);
66 ok(change != INVALID_HANDLE_VALUE, "FindFirstChangeNotification error: %d\n", GetLastError());
68 thread = CreateThread(NULL, 0, NotificationThread, change, 0, &threadId);
69 ok(thread != NULL, "CreateThread error: %d\n", GetLastError());
74 static DWORD FinishNotificationThread(HANDLE thread)
76 DWORD status, exitcode;
78 status = WaitForSingleObject(thread, 5000);
79 ok(status == WAIT_OBJECT_0, "WaitForSingleObject status %d error %d\n", status, GetLastError());
81 ok(GetExitCodeThread(thread, &exitcode), "Could not retrieve thread exit code\n");
87 static void test_FindFirstChangeNotification(void)
89 HANDLE change, file, thread;
90 DWORD attributes, count;
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";
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());
106 if (0) /* This documents win2k behavior. It crashes on win98. */
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());
113 ret = FindNextChangeNotification(NULL);
114 ok(!ret && GetLastError() == ERROR_INVALID_HANDLE, "FindNextChangeNotification error: %d\n",
117 ret = FindCloseChangeNotification(NULL);
118 ok(!ret && GetLastError() == ERROR_INVALID_HANDLE, "FindCloseChangeNotification error: %d\n",
121 ret = GetTempPathA(MAX_PATH, workdir);
122 ok(ret, "GetTempPathA error: %d\n", GetLastError());
124 lstrcatA(workdir, "testFileChangeNotification");
126 ret = CreateDirectoryA(workdir, NULL);
127 ok(ret, "CreateDirectoryA error: %d\n", GetLastError());
129 ret = GetTempFileNameA(workdir, prefix, 0, filename1);
130 ok(ret, "GetTempFileNameA error: %d\n", GetLastError());
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());
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());
144 lstrcpyA(dirname1, filename1);
145 lstrcatA(dirname1, "dir");
147 lstrcpyA(dirname2, dirname1);
148 lstrcatA(dirname2, "new");
150 ret = CreateDirectoryA(dirname1, NULL);
151 ok(ret, "CreateDirectoryA error: %d\n", GetLastError());
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);
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);
167 /* functional checks */
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");
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");
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");
187 lstrcpyA(filename2, filename1);
188 lstrcatA(filename2, "new");
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");
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");
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");
211 attributes = GetFileAttributesA(filename2);
212 ok(attributes != INVALID_FILE_ATTRIBUTES, "GetFileAttributesA error: %d\n", GetLastError());
213 attributes &= FILE_ATTRIBUTE_READONLY;
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");
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");
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");
246 ret = DeleteFileA(filename2);
247 ok(ret, "DeleteFileA error: %d\n", GetLastError());
249 ret = RemoveDirectoryA(workdir);
250 ok(ret, "RemoveDirectoryA error: %d\n", GetLastError());
253 /* this test concentrates more on the wait behaviour of the handle */
254 static void test_ffcn(void)
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 };
263 SetLastError(0xdeadbeef);
264 r = GetTempPathW( MAX_PATH, path );
265 if (!r && (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED))
267 win_skip("GetTempPathW is not implemented\n");
270 ok( r != 0, "temp path failed\n");
274 lstrcatW( path, szBoo );
275 lstrcpyW( subdir, path );
276 lstrcatW( subdir, szHoo );
278 RemoveDirectoryW( subdir );
279 RemoveDirectoryW( path );
281 r = CreateDirectoryW(path, NULL);
282 ok( r == TRUE, "failed to create directory\n");
284 filter = FILE_NOTIFY_CHANGE_FILE_NAME;
285 filter |= FILE_NOTIFY_CHANGE_DIR_NAME;
287 handle = FindFirstChangeNotificationW( path, 1, filter);
288 ok( handle != INVALID_HANDLE_VALUE, "invalid handle\n");
290 r = WaitForSingleObject( handle, 0 );
291 ok( r == STATUS_TIMEOUT, "should time out\n");
293 r = CreateDirectoryW( subdir, NULL );
294 ok( r == TRUE, "failed to create subdir\n");
296 r = WaitForSingleObject( handle, 0 );
297 ok( r == WAIT_OBJECT_0, "should be ready\n");
299 r = WaitForSingleObject( handle, 0 );
300 ok( r == WAIT_OBJECT_0, "should be ready\n");
302 r = FindNextChangeNotification(handle);
303 ok( r == TRUE, "find next failed\n");
305 r = WaitForSingleObject( handle, 0 );
306 ok( r == STATUS_TIMEOUT, "should time out\n");
308 r = RemoveDirectoryW( subdir );
309 ok( r == TRUE, "failed to remove subdir\n");
311 r = WaitForSingleObject( handle, 0 );
312 ok( r == WAIT_OBJECT_0, "should be ready\n");
314 r = WaitForSingleObject( handle, 0 );
315 ok( r == WAIT_OBJECT_0, "should be ready\n");
317 r = FindNextChangeNotification(handle);
318 ok( r == TRUE, "find next failed\n");
320 r = FindNextChangeNotification(handle);
321 ok( r == TRUE, "find next failed\n");
323 r = FindCloseChangeNotification(handle);
324 ok( r == TRUE, "should succeed\n");
326 r = RemoveDirectoryW( path );
327 ok( r == TRUE, "failed to remove dir\n");
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)
335 DWORD filter, threadId, status, exitcode;
339 r = GetTempPathA(MAX_PATH, path);
340 ok(r, "GetTempPathA error: %d\n", GetLastError());
342 lstrcatA(path, "ffcnTestMultipleThreads");
344 RemoveDirectoryA(path);
346 r = CreateDirectoryA(path, NULL);
347 ok(r, "CreateDirectoryA error: %d\n", GetLastError());
349 filter = FILE_NOTIFY_CHANGE_FILE_NAME;
350 filter |= FILE_NOTIFY_CHANGE_DIR_NAME;
352 handles[0] = FindFirstChangeNotificationA(path, FALSE, filter);
353 ok(handles[0] != INVALID_HANDLE_VALUE, "FindFirstChangeNotification error: %d\n", GetLastError());
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) */
358 /* Create our notification thread */
359 handles[1] = CreateThread(NULL, 0, NotificationThread, handles[0], 0,
361 ok(handles[1] != NULL, "CreateThread error: %d\n", GetLastError());
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");
368 r = RemoveDirectoryA( path );
369 ok( r == TRUE, "failed to remove dir\n");
372 typedef BOOL (WINAPI *fnReadDirectoryChangesW)(HANDLE,LPVOID,DWORD,BOOL,DWORD,
373 LPDWORD,LPOVERLAPPED,LPOVERLAPPED_COMPLETION_ROUTINE);
374 fnReadDirectoryChangesW pReadDirectoryChangesW;
376 static void test_readdirectorychanges(void)
380 DWORD fflags, filter = 0, r, dwCount;
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;
388 if (!pReadDirectoryChangesW)
390 win_skip("ReadDirectoryChangesW is not available\n");
394 SetLastError(0xdeadbeef);
395 r = GetTempPathW( MAX_PATH, path );
396 if (!r && (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED))
398 win_skip("GetTempPathW is not implemented\n");
401 ok( r != 0, "temp path failed\n");
405 lstrcatW( path, szBoo );
406 lstrcpyW( subdir, path );
407 lstrcatW( subdir, szHoo );
409 lstrcpyW( subsubdir, path );
410 lstrcatW( subsubdir, szGa );
412 RemoveDirectoryW( subsubdir );
413 RemoveDirectoryW( subdir );
414 RemoveDirectoryW( path );
416 r = CreateDirectoryW(path, NULL);
417 ok( r == TRUE, "failed to create directory\n");
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");
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");
430 ov.hEvent = CreateEvent( NULL, 1, 0, NULL );
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");
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");
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;
451 SetLastError(0xd0b00b00);
454 memset( buffer, 0, sizeof buffer );
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");
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");
464 r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,TRUE,filter,NULL,&ov,NULL);
465 ok(r==TRUE, "should return true\n");
467 r = WaitForSingleObject( ov.hEvent, 10 );
468 ok( r == STATUS_TIMEOUT, "should timeout\n" );
470 r = CreateDirectoryW( subdir, NULL );
471 ok( r == TRUE, "failed to create directory\n");
473 r = WaitForSingleObject( ov.hEvent, 1000 );
474 ok( r == WAIT_OBJECT_0, "event should be ready\n" );
476 ok( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
477 ok( ov.InternalHigh == 0x12, "ov.InternalHigh wrong\n");
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" );
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");
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");
495 filter = FILE_NOTIFY_CHANGE_SIZE;
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");
506 ok( ov.Internal == STATUS_PENDING, "ov.Internal wrong\n");
507 ok( ov.InternalHigh == 1, "ov.InternalHigh wrong\n");
509 r = WaitForSingleObject( ov.hEvent, 0 );
510 ok( r == STATUS_TIMEOUT, "should timeout\n" );
512 r = RemoveDirectoryW( subdir );
513 ok( r == TRUE, "failed to remove directory\n");
515 r = WaitForSingleObject( ov.hEvent, 1000 );
516 ok( r == WAIT_OBJECT_0, "should be ready\n" );
518 ok( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
519 ok( ov.InternalHigh == 0x12, "ov.InternalHigh wrong\n");
521 if (ov.Internal == STATUS_SUCCESS)
523 r = GetOverlappedResult( hdir, &ov, &dwCount, TRUE );
524 ok( r == TRUE, "getoverlappedresult failed\n");
525 ok( dwCount == 0x12, "count wrong\n");
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" );
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");
538 r = CreateDirectoryW( subdir, NULL );
539 ok( r == TRUE, "failed to create directory\n");
541 r = WaitForSingleObject( ov.hEvent, 1000 );
542 ok( r == WAIT_OBJECT_0, "should be ready\n" );
544 ok( ov.Internal == STATUS_NOTIFY_ENUM_DIR, "ov.Internal wrong\n");
545 ok( ov.InternalHigh == 0, "ov.InternalHigh wrong\n");
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");
551 r = CreateDirectoryW( subsubdir, NULL );
552 ok( r == TRUE, "failed to create directory\n");
554 r = WaitForSingleObject( ov.hEvent, 1000 );
555 ok( r == WAIT_OBJECT_0, "should be ready\n" );
557 ok( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
558 ok( ov.InternalHigh == 0x18, "ov.InternalHigh wrong\n");
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" );
566 r = RemoveDirectoryW( subsubdir );
567 ok( r == TRUE, "failed to remove directory\n");
571 r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
572 ok(r==TRUE, "should return true\n");
574 r = RemoveDirectoryW( subdir );
575 ok( r == TRUE, "failed to remove directory\n");
577 r = WaitForSingleObject( ov.hEvent, 1000 );
578 ok( r == WAIT_OBJECT_0, "should be ready\n" );
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)
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);
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" );
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 );
599 r = RemoveDirectoryW( path );
600 ok( r == TRUE, "failed to remove directory\n");
603 /* show the behaviour when a null buffer is passed */
604 static void test_readdirectorychanges_null(void)
609 DWORD fflags, filter = 0;
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;
616 if (!pReadDirectoryChangesW)
618 win_skip("ReadDirectoryChangesW is not available\n");
621 SetLastError(0xdeadbeef);
622 r = GetTempPathW( MAX_PATH, path );
623 if (!r && (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED))
625 win_skip("GetTempPathW is not implemented\n");
628 ok( r != 0, "temp path failed\n");
632 lstrcatW( path, szBoo );
633 lstrcpyW( subdir, path );
634 lstrcatW( subdir, szHoo );
636 RemoveDirectoryW( subdir );
637 RemoveDirectoryW( path );
639 r = CreateDirectoryW(path, NULL);
640 ok( r == TRUE, "failed to create directory\n");
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");
648 ov.hEvent = CreateEvent( NULL, 1, 0, NULL );
650 filter = FILE_NOTIFY_CHANGE_FILE_NAME;
651 filter |= FILE_NOTIFY_CHANGE_DIR_NAME;
653 SetLastError(0xd0b00b00);
656 memset( buffer, 0, sizeof buffer );
658 r = pReadDirectoryChangesW(hdir,NULL,0,FALSE,filter,NULL,&ov,NULL);
659 ok(r==TRUE, "should return true\n");
661 r = WaitForSingleObject( ov.hEvent, 0 );
662 ok( r == STATUS_TIMEOUT, "should timeout\n" );
664 r = CreateDirectoryW( subdir, NULL );
665 ok( r == TRUE, "failed to create directory\n");
667 r = WaitForSingleObject( ov.hEvent, 0 );
668 ok( r == WAIT_OBJECT_0, "event should be ready\n" );
670 ok( ov.Internal == STATUS_NOTIFY_ENUM_DIR, "ov.Internal wrong\n");
671 ok( ov.InternalHigh == 0, "ov.InternalHigh wrong\n");
676 S(U(ov)).OffsetHigh = 0;
677 memset( buffer, 0, sizeof buffer );
679 r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,filter,NULL,&ov,NULL);
680 ok(r==TRUE, "should return true\n");
682 r = WaitForSingleObject( ov.hEvent, 0 );
683 ok( r == STATUS_TIMEOUT, "should timeout\n" );
685 r = RemoveDirectoryW( subdir );
686 ok( r == TRUE, "failed to remove directory\n");
688 r = WaitForSingleObject( ov.hEvent, 1000 );
689 ok( r == WAIT_OBJECT_0, "should be ready\n" );
691 ok( ov.Internal == STATUS_NOTIFY_ENUM_DIR, "ov.Internal wrong\n");
692 ok( ov.InternalHigh == 0, "ov.InternalHigh wrong\n");
694 pfni = (PFILE_NOTIFY_INFORMATION) buffer;
695 ok( pfni->NextEntryOffset == 0, "offset wrong\n" );
699 r = RemoveDirectoryW( path );
700 ok( r == TRUE, "failed to remove directory\n");
703 static void test_readdirectorychanges_filedir(void)
708 DWORD fflags, filter = 0;
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;
716 SetLastError(0xdeadbeef);
717 r = GetTempPathW( MAX_PATH, path );
718 if (!r && (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED))
720 win_skip("GetTempPathW is not implemented\n");
723 ok( r != 0, "temp path failed\n");
727 lstrcatW( path, szBoo );
728 lstrcpyW( subdir, path );
729 lstrcatW( subdir, szHoo );
731 lstrcpyW( file, path );
732 lstrcatW( file, szFoo );
735 RemoveDirectoryW( subdir );
736 RemoveDirectoryW( path );
738 r = CreateDirectoryW(path, NULL);
739 ok( r == TRUE, "failed to create directory\n");
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");
747 ov.hEvent = CreateEvent( NULL, 0, 0, NULL );
749 filter = FILE_NOTIFY_CHANGE_FILE_NAME;
751 r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,TRUE,filter,NULL,&ov,NULL);
752 ok(r==TRUE, "should return true\n");
754 r = WaitForSingleObject( ov.hEvent, 10 );
755 ok( r == WAIT_TIMEOUT, "should timeout\n" );
757 r = CreateDirectoryW( subdir, NULL );
758 ok( r == TRUE, "failed to create directory\n");
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");
764 r = WaitForSingleObject( ov.hEvent, 1000 );
765 ok( r == WAIT_OBJECT_0, "event should be ready\n" );
767 ok( ov.Internal == STATUS_SUCCESS, "ov.Internal wrong\n");
768 ok( ov.InternalHigh == 0x12, "ov.InternalHigh wrong\n");
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" );
776 r = DeleteFileW( file );
777 ok( r == TRUE, "failed to delete file\n");
779 r = RemoveDirectoryW( subdir );
780 ok( r == TRUE, "failed to remove directory\n");
784 r = RemoveDirectoryW( path );
785 ok( r == TRUE, "failed to remove directory\n");
788 static void test_ffcn_directory_overlap(void)
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];
796 /* Setup directory hierarchy */
797 ret = GetTempPathA(MAX_PATH, workdir);
798 ok((ret > 0) && (ret <= MAX_PATH),
799 "GetTempPathA error: %d\n", GetLastError());
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());
806 lstrcpyA(parentdir, tempfile);
807 ret = CreateDirectoryA(parentdir, NULL);
808 ok(ret, "CreateDirectoryA error: %d\n", GetLastError());
810 lstrcpyA(childdir, parentdir);
811 lstrcatA(childdir, "\\c");
812 ret = CreateDirectoryA(childdir, NULL);
813 ok(ret, "CreateDirectoryA error: %d\n", GetLastError());
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);
823 /* Create a file in child */
824 ret = GetTempFileNameA(childdir, "fcn", 0, tempfile);
825 ok(ret, "GetTempFileNameA error: %d\n", GetLastError());
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");
833 ret = DeleteFileA(tempfile);
834 ok(ret, "DeleteFileA error: %d\n", GetLastError());
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());
848 ret = FindCloseChangeNotification(parent_watch);
849 ok(ret, "FindCloseChangeNotification error: %d\n", GetLastError());
851 child_thread = CreateThread(NULL, 0, NotificationThread, child_watch, 0,
853 ok(child_thread != NULL, "CreateThread error: %d\n", GetLastError());
855 /* Create a file in child */
856 ret = GetTempFileNameA(childdir, "fcn", 0, tempfile);
857 ok(ret, "GetTempFileNameA error: %d\n", GetLastError());
859 /* Child watch should trigger */
860 ret = FinishNotificationThread(child_thread);
861 ok(ret, "Missed child notification\n");
864 ret = DeleteFileA(tempfile);
865 ok(ret, "DeleteFileA error: %d\n", GetLastError());
867 ret = RemoveDirectoryA(childdir);
868 ok(ret, "RemoveDirectoryA error: %d\n", GetLastError());
870 ret = RemoveDirectoryA(parentdir);
871 ok(ret, "RemoveDirectoryA error: %d\n", GetLastError());
876 HMODULE hkernel32 = GetModuleHandle("kernel32");
877 pReadDirectoryChangesW = (fnReadDirectoryChangesW)
878 GetProcAddress(hkernel32, "ReadDirectoryChangesW");
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
884 test_FindFirstChangeNotification();
886 test_readdirectorychanges();
887 test_readdirectorychanges_null();
888 test_readdirectorychanges_filedir();
889 test_ffcn_directory_overlap();