1 /*
2 * Tests for file change notification functions
3 *
4 * Copyright (c) 2004 Hans Leidekker
5 * Copyright 2006 Mike McCormack for CodeWeavers
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 */
21
22 /* TODO: - security attribute changes
23 * - compound filter and multiple notifications
24 * - subtree notifications
25 * - non-documented flags FILE_NOTIFY_CHANGE_LAST_ACCESS and
26 * FILE_NOTIFY_CHANGE_CREATION
27 */
28
29 #include <stdarg.h>
30 #include <stdio.h>
31
32 #include "ntstatus.h"
33 #define WIN32_NO_STATUS
34 #include "wine/test.h"
35 #include <windef.h>
36 #include <winbase.h>
37 #include <winternl.h>
38
39 static DWORD CALLBACK NotificationThread(LPVOID arg)
40 {
41 HANDLE change = (HANDLE) arg;
42 BOOL notified = FALSE;
43 BOOL ret = FALSE;
44 DWORD status;
45
46 status = WaitForSingleObject(change, 100);
47
48 if (status == WAIT_OBJECT_0 ) {
49 notified = TRUE;
50 ret = FindNextChangeNotification(change);
51 }
52
53 ret = FindCloseChangeNotification(change);
54 ok( ret, "FindCloseChangeNotification error: %d\n",
55 GetLastError());
56
57 ExitThread((DWORD)notified);
58 }
59
60 static HANDLE StartNotificationThread(LPCSTR path, BOOL subtree, DWORD flags)
61 {
62 HANDLE change, thread;
63 DWORD threadId;
64
65 change = FindFirstChangeNotificationA(path, subtree, flags);
66 ok(change != INVALID_HANDLE_VALUE, "FindFirstChangeNotification error: %d\n", GetLastError());
67
68 thread = CreateThread(NULL, 0, NotificationThread, (LPVOID)change,
69 0, &threadId);
70 ok(thread != NULL, "CreateThread error: %d\n", GetLastError());
71
72 return thread;
73 }
74
75 static DWORD FinishNotificationThread(HANDLE thread)
76 {
77 DWORD status, exitcode;
78
79 status = WaitForSingleObject(thread, 5000);
80 ok(status == WAIT_OBJECT_0, "WaitForSingleObject status %d error %d\n", status, GetLastError());
81
82 ok(GetExitCodeThread(thread, &exitcode), "Could not retrieve thread exit code\n");
83
84 return exitcode;
85 }
86
87 static void test_FindFirstChangeNotification(void)
88 {
89 HANDLE change, file, thread;
90 DWORD attributes, count;
91 BOOL ret;
92
93 char workdir[MAX_PATH], dirname1[MAX_PATH], dirname2[MAX_PATH];
94 char filename1[MAX_PATH], filename2[MAX_PATH];
95 static const char prefix[] = "FCN";
96 char buffer[2048];
97
98 /* pathetic checks */
99
100 change = FindFirstChangeNotificationA("not-a-file", FALSE, FILE_NOTIFY_CHANGE_FILE_NAME);
101 ok(change == INVALID_HANDLE_VALUE, "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 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, (LPVOID)handles[0],
360 0, &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 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 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 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 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 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;
<