kernel32: FindFirstChangeNotification needs a static IO_STATUS_BLOCK.
[wine] / dlls / kernel / tests / 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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 "wine/test.h"
33 #include <windef.h>
34 #include <winbase.h>
35
36 static DWORD CALLBACK NotificationThread(LPVOID arg)
37 {
38     HANDLE change = (HANDLE) arg;
39     BOOL ret = FALSE;
40     DWORD status;
41
42     status = WaitForSingleObject(change, 100);
43
44     if (status == WAIT_OBJECT_0 ) {
45         ret = FindNextChangeNotification(change);
46     }
47
48     ret = FindCloseChangeNotification(change);
49     ok( ret, "FindCloseChangeNotification error: %ld\n",
50        GetLastError());
51
52     ExitThread((DWORD)ret);
53 }
54
55 static HANDLE StartNotificationThread(LPCSTR path, BOOL subtree, DWORD flags)
56 {
57     HANDLE change, thread;
58     DWORD threadId;
59
60     change = FindFirstChangeNotificationA(path, subtree, flags);
61     ok(change != INVALID_HANDLE_VALUE, "FindFirstChangeNotification error: %ld\n", GetLastError());
62
63     thread = CreateThread(NULL, 0, NotificationThread, (LPVOID)change,
64                           0, &threadId);
65     ok(thread != INVALID_HANDLE_VALUE, "CreateThread error: %ld\n", GetLastError());
66
67     return thread;
68 }
69
70 static DWORD FinishNotificationThread(HANDLE thread)
71 {
72     DWORD status, exitcode;
73
74     status = WaitForSingleObject(thread, 5000);
75     ok(status == WAIT_OBJECT_0, "WaitForSingleObject status %ld error %ld\n", status, GetLastError());
76
77     ok(GetExitCodeThread(thread, &exitcode), "Could not retrieve thread exit code\n");
78
79     return exitcode;
80 }
81
82 static void test_FindFirstChangeNotification(void)
83 {
84     HANDLE change, file, thread;
85     DWORD attributes, count;
86     BOOL ret;
87
88     char workdir[MAX_PATH], dirname1[MAX_PATH], dirname2[MAX_PATH];
89     char filename1[MAX_PATH], filename2[MAX_PATH];
90     static const char prefix[] = "FCN";
91     char buffer[2048];
92
93     /* pathetic checks */
94
95     change = FindFirstChangeNotificationA("not-a-file", FALSE, FILE_NOTIFY_CHANGE_FILE_NAME);
96     ok(change == INVALID_HANDLE_VALUE && GetLastError() == ERROR_FILE_NOT_FOUND,
97        "FindFirstChangeNotification error: %ld\n", GetLastError());
98
99     if (0) /* This documents win2k behavior. It crashes on win98. */
100     { 
101         change = FindFirstChangeNotificationA(NULL, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME);
102         ok(change == NULL && GetLastError() == ERROR_PATH_NOT_FOUND,
103         "FindFirstChangeNotification error: %ld\n", GetLastError());
104     }
105
106     ret = FindNextChangeNotification(NULL);
107     ok(!ret && GetLastError() == ERROR_INVALID_HANDLE, "FindNextChangeNotification error: %ld\n",
108        GetLastError());
109
110     ret = FindCloseChangeNotification(NULL);
111     ok(!ret && GetLastError() == ERROR_INVALID_HANDLE, "FindCloseChangeNotification error: %ld\n",
112        GetLastError());
113
114     ret = GetTempPathA(MAX_PATH, workdir);
115     ok(ret, "GetTempPathA error: %ld\n", GetLastError());
116
117     lstrcatA(workdir, "testFileChangeNotification");
118
119     ret = CreateDirectoryA(workdir, NULL);
120     ok(ret, "CreateDirectoryA error: %ld\n", GetLastError());
121
122     ret = GetTempFileNameA(workdir, prefix, 0, filename1);
123     ok(ret, "GetTempFileNameA error: %ld\n", GetLastError());
124
125     file = CreateFileA(filename1, GENERIC_WRITE|GENERIC_READ, 0, NULL, CREATE_ALWAYS,
126                        FILE_ATTRIBUTE_NORMAL, 0);
127     ok(file != INVALID_HANDLE_VALUE, "CreateFileA error: %ld\n", GetLastError());
128     ret = CloseHandle(file);
129     ok( ret, "CloseHandle error: %ld\n", GetLastError());
130
131     /* Try to register notification for a file. win98 and win2k behave differently here */
132     change = FindFirstChangeNotificationA(filename1, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME);
133     ok(change == INVALID_HANDLE_VALUE && (GetLastError() == ERROR_DIRECTORY ||
134                                           GetLastError() == ERROR_FILE_NOT_FOUND),
135        "FindFirstChangeNotification error: %ld\n", GetLastError());
136
137     lstrcpyA(dirname1, filename1);
138     lstrcatA(dirname1, "dir");
139
140     lstrcpyA(dirname2, dirname1);
141     lstrcatA(dirname2, "new");
142
143     ret = CreateDirectoryA(dirname1, NULL);
144     ok(ret, "CreateDirectoryA error: %ld\n", GetLastError());
145
146     /* What if we move the directory we registered notification for? */
147     thread = StartNotificationThread(dirname1, FALSE, FILE_NOTIFY_CHANGE_DIR_NAME);
148     ret = MoveFileA(dirname1, dirname2);
149     ok(ret, "MoveFileA error: %ld\n", GetLastError());
150     ok(FinishNotificationThread(thread), "Missed notification\n");
151
152     /* What if we remove the directory we registered notification for? */
153     thread = StartNotificationThread(dirname2, FALSE, FILE_NOTIFY_CHANGE_DIR_NAME);
154     ret = RemoveDirectoryA(dirname2);
155     ok(ret, "RemoveDirectoryA error: %ld\n", GetLastError());
156
157     /* win98 and win2k behave differently here */
158     ret = FinishNotificationThread(thread);
159     ok(ret || !ret, "You'll never read this\n");
160
161     /* functional checks */
162
163     /* Create a directory */
164     thread = StartNotificationThread(workdir, FALSE, FILE_NOTIFY_CHANGE_DIR_NAME);
165     ret = CreateDirectoryA(dirname1, NULL);
166     ok(ret, "CreateDirectoryA error: %ld\n", GetLastError());
167     ok(FinishNotificationThread(thread), "Missed notification\n");
168
169     /* Rename a directory */
170     thread = StartNotificationThread(workdir, FALSE, FILE_NOTIFY_CHANGE_DIR_NAME);
171     ret = MoveFileA(dirname1, dirname2);
172     ok(ret, "MoveFileA error: %ld\n", GetLastError());
173     ok(FinishNotificationThread(thread), "Missed notification\n");
174
175     /* Delete a directory */
176     thread = StartNotificationThread(workdir, FALSE, FILE_NOTIFY_CHANGE_DIR_NAME);
177     ret = RemoveDirectoryA(dirname2);
178     ok(ret, "RemoveDirectoryA error: %ld\n", GetLastError());
179     ok(FinishNotificationThread(thread), "Missed notification\n");
180
181     lstrcpyA(filename2, filename1);
182     lstrcatA(filename2, "new");
183
184     /* Rename a file */
185     thread = StartNotificationThread(workdir, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME);
186     ret = MoveFileA(filename1, filename2);
187     ok(ret, "MoveFileA error: %ld\n", GetLastError());
188     ok(FinishNotificationThread(thread), "Missed notification\n");
189
190     /* Delete a file */
191     thread = StartNotificationThread(workdir, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME);
192     ret = DeleteFileA(filename2);
193     ok(ret, "DeleteFileA error: %ld\n", GetLastError());
194     ok(FinishNotificationThread(thread), "Missed notification\n");
195
196     /* Create a file */
197     thread = StartNotificationThread(workdir, FALSE, FILE_NOTIFY_CHANGE_FILE_NAME);
198     file = CreateFileA(filename2, GENERIC_WRITE|GENERIC_READ, 0, NULL, CREATE_ALWAYS, 
199                        FILE_ATTRIBUTE_NORMAL, 0);
200     ok(file != INVALID_HANDLE_VALUE, "CreateFileA error: %ld\n", GetLastError());
201     ret = CloseHandle(file);
202     ok( ret, "CloseHandle error: %ld\n", GetLastError());
203     ok(FinishNotificationThread(thread), "Missed notification\n");
204
205     attributes = GetFileAttributesA(filename2);
206     ok(attributes != INVALID_FILE_ATTRIBUTES, "GetFileAttributesA error: %ld\n", GetLastError());
207     attributes &= FILE_ATTRIBUTE_READONLY;
208
209     /* Change file attributes */
210     thread = StartNotificationThread(workdir, FALSE, FILE_NOTIFY_CHANGE_ATTRIBUTES);
211     ret = SetFileAttributesA(filename2, attributes);
212     ok(ret, "SetFileAttributesA error: %ld\n", GetLastError());
213     ok(FinishNotificationThread(thread), "Missed notification\n");
214
215     /* Change last write time by writing to a file */
216     thread = StartNotificationThread(workdir, FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE);
217     file = CreateFileA(filename2, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 
218                        FILE_ATTRIBUTE_NORMAL, 0);
219     ok(file != INVALID_HANDLE_VALUE, "CreateFileA error: %ld\n", GetLastError());
220     ret = WriteFile(file, buffer, sizeof(buffer), &count, NULL);
221     ok(ret && count == sizeof(buffer), "WriteFile error: %ld\n", GetLastError());
222     ret = CloseHandle(file);
223     ok( ret, "CloseHandle error: %ld\n", GetLastError());
224     ok(FinishNotificationThread(thread), "Missed notification\n");
225
226     /* Change file size by truncating a file */
227     thread = StartNotificationThread(workdir, FALSE, FILE_NOTIFY_CHANGE_SIZE);
228     file = CreateFileA(filename2, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 
229                        FILE_ATTRIBUTE_NORMAL, 0);
230     ok(file != INVALID_HANDLE_VALUE, "CreateFileA error: %ld\n", GetLastError());
231     ret = WriteFile(file, buffer, sizeof(buffer) / 2, &count, NULL);
232     ok(ret && count == sizeof(buffer) / 2, "WriteFileA error: %ld\n", GetLastError());
233     ret = CloseHandle(file);
234     ok( ret, "CloseHandle error: %ld\n", GetLastError());
235     ok(FinishNotificationThread(thread), "Missed notification\n");
236
237     /* clean up */
238     
239     ret = DeleteFileA(filename2);
240     ok(ret, "DeleteFileA error: %ld\n", GetLastError());
241
242     ret = RemoveDirectoryA(workdir);
243     ok(ret, "RemoveDirectoryA error: %ld\n", GetLastError());
244 }
245
246 /* this test concentrates more on the wait behaviour of the handle */
247 static void test_ffcn(void)
248 {
249     DWORD filter;
250     HANDLE handle;
251     LONG r;
252     WCHAR path[MAX_PATH], subdir[MAX_PATH];
253     static const WCHAR szBoo[] = { '\\','b','o','o',0 };
254     static const WCHAR szHoo[] = { '\\','h','o','o',0 };
255
256     r = GetTempPathW( MAX_PATH, path );
257     ok( r != 0, "temp path failed\n");
258     if (!r)
259         return;
260
261     lstrcatW( path, szBoo );
262     lstrcpyW( subdir, path );
263     lstrcatW( subdir, szHoo );
264
265     RemoveDirectoryW( subdir );
266     RemoveDirectoryW( path );
267     
268     r = CreateDirectoryW(path, NULL);
269     ok( r == TRUE, "failed to create directory\n");
270
271     filter = FILE_NOTIFY_CHANGE_FILE_NAME;
272     filter |= FILE_NOTIFY_CHANGE_DIR_NAME;
273
274     handle = FindFirstChangeNotificationW( path, 1, filter);
275     ok( handle != INVALID_HANDLE_VALUE, "invalid handle\n");
276
277     r = WaitForSingleObject( handle, 0 );
278     ok( r == STATUS_TIMEOUT, "should time out\n");
279
280     r = CreateDirectoryW( subdir, NULL );
281     ok( r == TRUE, "failed to create subdir\n");
282
283     r = WaitForSingleObject( handle, 0 );
284     ok( r == WAIT_OBJECT_0, "should be ready\n");
285
286     r = WaitForSingleObject( handle, 0 );
287     ok( r == WAIT_OBJECT_0, "should be ready\n");
288
289     r = FindNextChangeNotification(handle);
290     ok( r == TRUE, "find next failed\n");
291
292     r = WaitForSingleObject( handle, 0 );
293     ok( r == STATUS_TIMEOUT, "should time out\n");
294
295     r = RemoveDirectoryW( subdir );
296     ok( r == TRUE, "failed to remove subdir\n");
297
298     r = WaitForSingleObject( handle, 0 );
299     ok( r == WAIT_OBJECT_0, "should be ready\n");
300
301     r = WaitForSingleObject( handle, 0 );
302     ok( r == WAIT_OBJECT_0, "should be ready\n");
303
304     r = FindNextChangeNotification(handle);
305     ok( r == TRUE, "find next failed\n");
306
307     r = FindNextChangeNotification(handle);
308     ok( r == TRUE, "find next failed\n");
309
310     r = FindCloseChangeNotification(handle);
311     ok( r == TRUE, "should succeed\n");
312
313     r = RemoveDirectoryW( path );
314     ok( r == TRUE, "failed to remove dir\n");
315 }
316
317 typedef BOOL (WINAPI *fnReadDirectoryChangesW)(HANDLE,LPVOID,DWORD,BOOL,DWORD,
318                          LPDWORD,LPOVERLAPPED,LPOVERLAPPED_COMPLETION_ROUTINE);
319
320 static void test_readdirectorychanges(void)
321 {
322     HANDLE hdir;
323     char buffer[0x1000];
324     DWORD fflags, filter = 0, r;
325     OVERLAPPED ov;
326     WCHAR path[MAX_PATH], subdir[MAX_PATH];
327     static const WCHAR szBoo[] = { '\\','b','o','o',0 };
328     static const WCHAR szHoo[] = { '\\','h','o','o',0 };
329     fnReadDirectoryChangesW pReadDirectoryChangesW;
330     HMODULE hkernel32;
331
332     hkernel32 = GetModuleHandle("kernel32");
333     pReadDirectoryChangesW = (fnReadDirectoryChangesW)
334         GetProcAddress(hkernel32, "ReadDirectoryChangesW");
335     if (!pReadDirectoryChangesW)
336         return;
337
338     r = GetTempPathW( MAX_PATH, path );
339     ok( r != 0, "temp path failed\n");
340     if (!r)
341         return;
342
343     lstrcatW( path, szBoo );
344     lstrcpyW( subdir, path );
345     lstrcatW( subdir, szHoo );
346
347     RemoveDirectoryW( subdir );
348     RemoveDirectoryW( path );
349     
350     r = CreateDirectoryW(path, NULL);
351     ok( r == TRUE, "failed to create directory\n");
352
353     SetLastError(0xd0b00b00);
354     r = pReadDirectoryChangesW(NULL,NULL,0,FALSE,0,NULL,NULL,NULL);
355     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
356     ok(r==FALSE, "should return false\n");
357
358     fflags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
359     hdir = CreateFileW(path, GENERIC_READ|SYNCHRONIZE, FILE_SHARE_READ, NULL, 
360                         OPEN_EXISTING, fflags, NULL);
361     ok( hdir != INVALID_HANDLE_VALUE, "failed to open directory\n");
362
363     ov.hEvent = CreateEvent( NULL, 0, 0, NULL );
364
365     SetLastError(0xd0b00b00);
366     r = pReadDirectoryChangesW(hdir,NULL,0,FALSE,0,NULL,NULL,NULL);
367     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
368     ok(r==FALSE, "should return false\n");
369
370     SetLastError(0xd0b00b00);
371     r = pReadDirectoryChangesW(hdir,NULL,0,FALSE,0,NULL,&ov,NULL);
372     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
373     ok(r==FALSE, "should return false\n");
374
375     filter = FILE_NOTIFY_CHANGE_FILE_NAME;
376     filter |= FILE_NOTIFY_CHANGE_DIR_NAME;
377     filter |= FILE_NOTIFY_CHANGE_ATTRIBUTES;
378     filter |= FILE_NOTIFY_CHANGE_SIZE;
379     filter |= FILE_NOTIFY_CHANGE_LAST_WRITE;
380     filter |= FILE_NOTIFY_CHANGE_LAST_ACCESS;
381     filter |= FILE_NOTIFY_CHANGE_CREATION;
382     filter |= FILE_NOTIFY_CHANGE_SECURITY;
383
384     SetLastError(0xd0b00b00);
385     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,-1,NULL,&ov,NULL);
386     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
387     ok(r==FALSE, "should return false\n");
388
389     r = pReadDirectoryChangesW(hdir,NULL,0,FALSE,filter,NULL,&ov,NULL);
390     ok(r==TRUE, "should return true\n");
391
392     r = WaitForSingleObject( ov.hEvent, 0 );
393     ok( r == STATUS_TIMEOUT, "should timeout\n" );
394
395     r = WaitForSingleObject( hdir, 0 );
396     ok( r == STATUS_TIMEOUT, "should timeout\n" );
397
398     r = CreateDirectoryW( subdir, NULL );
399     ok( r == TRUE, "failed to create directory\n");
400
401     r = WaitForSingleObject( hdir, 0 );
402     ok( r == STATUS_TIMEOUT, "should timeout\n" );
403
404     r = WaitForSingleObject( ov.hEvent, 0 );
405     ok( r == WAIT_OBJECT_0, "event should be ready\n" );
406
407     SetLastError(0xd0b00b00);
408     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,0,NULL,NULL,NULL);
409     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
410     ok(r==FALSE, "should return false\n");
411
412     r = pReadDirectoryChangesW(hdir,buffer,sizeof buffer,FALSE,0,NULL,&ov,NULL);
413     ok(GetLastError()==ERROR_INVALID_PARAMETER,"last error wrong\n");
414     ok(r==FALSE, "should return false\n");
415
416     filter = FILE_NOTIFY_CHANGE_SIZE;
417
418     CloseHandle( ov.hEvent );
419     ov.hEvent = NULL;
420     r = pReadDirectoryChangesW(hdir,NULL,0,FALSE,filter,NULL,&ov,NULL);
421     ok(r==TRUE, "should return true\n");
422
423     r = WaitForSingleObject( hdir, 0 );
424     ok( r == STATUS_TIMEOUT, "should timeout\n" );
425
426     r = RemoveDirectoryW( subdir );
427     ok( r == TRUE, "failed to remove directory\n");
428
429     r = WaitForSingleObject( hdir, 0 );
430     ok( r == WAIT_OBJECT_0, "should be ready\n" );
431
432     r = WaitForSingleObject( hdir, 0 );
433     ok( r == WAIT_OBJECT_0, "should be ready\n" );
434
435     CloseHandle(hdir);
436
437     r = RemoveDirectoryW( path );
438     ok( r == TRUE, "failed to remove directory\n");
439 }
440
441
442 START_TEST(change)
443 {
444     test_FindFirstChangeNotification();
445     test_ffcn();
446     test_readdirectorychanges();
447 }