1 /* GIO - GLib Input, Output and Streaming Library
2 *
3 * Copyright (C) 2006-2007 Red Hat, Inc.
4 * Copyright (C) 2015 Chun-wei Fan
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General
17 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
18 *
19 * Author: Vlad Grecescu <b100dian@gmail.com>
20 * Author: Chun-wei Fan <fanc999@yahoo.com.tw>
21 *
22 */
23
24 #include "config.h"
25
26 #include "gwin32fsmonitorutils.h"
27 #include "gio/gfile.h"
28
29 #include <windows.h>
30
31 #define MAX_PATH_LONG 32767 /* Support Paths longer than MAX_PATH (260) characters */
32
33 static gboolean
34 g_win32_fs_monitor_handle_event (GWin32FSMonitorPrivate *monitor,
35 const gchar *filename,
36 PFILE_NOTIFY_INFORMATION pfni)
37 {
38 GFileMonitorEvent fme;
39 PFILE_NOTIFY_INFORMATION pfni_next;
40 WIN32_FILE_ATTRIBUTE_DATA attrib_data = {0, };
41 gchar *renamed_file = NULL;
42
43 switch (pfni->Action)
44 {
45 case FILE_ACTION_ADDED:
46 fme = G_FILE_MONITOR_EVENT_CREATED;
47 break;
48
49 case FILE_ACTION_REMOVED:
50 fme = G_FILE_MONITOR_EVENT_DELETED;
51 break;
52
53 case FILE_ACTION_MODIFIED:
54 {
55 gboolean success_attribs = GetFileAttributesExW (monitor->wfullpath_with_long_prefix,
56 GetFileExInfoStandard,
57 &attrib_data);
58
59 if (monitor->file_attribs != INVALID_FILE_ATTRIBUTES &&
60 success_attribs &&
61 attrib_data.dwFileAttributes != monitor->file_attribs)
62 fme = G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED;
63 else
64 fme = G_FILE_MONITOR_EVENT_CHANGED;
65
66 monitor->file_attribs = attrib_data.dwFileAttributes;
67 }
68 break;
69
70 case FILE_ACTION_RENAMED_OLD_NAME:
71 if (pfni->NextEntryOffset != 0)
72 {
73 /* If the file was renamed in the same directory, we would get a
74 * FILE_ACTION_RENAMED_NEW_NAME action in the next FILE_NOTIFY_INFORMATION
75 * structure.
76 */
77 glong file_name_len = 0;
78
79 pfni_next = (PFILE_NOTIFY_INFORMATION) ((BYTE*)pfni + pfni->NextEntryOffset);
80 renamed_file = g_utf16_to_utf8 (pfni_next->FileName, pfni_next->FileNameLength / sizeof(WCHAR), NULL, &file_name_len, NULL);
81 if (pfni_next->Action == FILE_ACTION_RENAMED_NEW_NAME)
82 fme = G_FILE_MONITOR_EVENT_RENAMED;
83 else
84 fme = G_FILE_MONITOR_EVENT_MOVED_OUT;
85 }
86 else
87 fme = G_FILE_MONITOR_EVENT_MOVED_OUT;
88 break;
89
90 case FILE_ACTION_RENAMED_NEW_NAME:
91 if (monitor->pfni_prev != NULL &&
92 monitor->pfni_prev->Action == FILE_ACTION_RENAMED_OLD_NAME)
93 {
94 /* don't bother sending events, was already sent (rename) */
95 fme = (GFileMonitorEvent) -1;
96 }
97 else
98 fme = G_FILE_MONITOR_EVENT_MOVED_IN;
99 break;
100
101 default:
102 /* The possible Windows actions are all above, so shouldn't get here */
103 g_assert_not_reached ();
104 break;
105 }
106
107 if (fme != (GFileMonitorEvent) -1)
108 return g_file_monitor_source_handle_event (monitor->fms,
109 fme,
110 filename,
111 renamed_file,
112 NULL,
113 g_get_monotonic_time ());
114 else
115 return FALSE;
116 }
117
118
119 static void CALLBACK
120 g_win32_fs_monitor_callback (DWORD error,
121 DWORD nBytes,
122 LPOVERLAPPED lpOverlapped)
123 {
124 gulong offset;
125 PFILE_NOTIFY_INFORMATION pfile_notify_walker;
126 GWin32FSMonitorPrivate *monitor = (GWin32FSMonitorPrivate *) lpOverlapped;
127
128 DWORD notify_filter = monitor->isfile ?
129 (FILE_NOTIFY_CHANGE_FILE_NAME |
130 FILE_NOTIFY_CHANGE_ATTRIBUTES |
131 FILE_NOTIFY_CHANGE_SIZE) :
132 (FILE_NOTIFY_CHANGE_FILE_NAME |
133 FILE_NOTIFY_CHANGE_DIR_NAME |
134 FILE_NOTIFY_CHANGE_ATTRIBUTES |
135 FILE_NOTIFY_CHANGE_SIZE);
136
137 /* If monitor->self is NULL the GWin32FileMonitor object has been destroyed. */
138 if (monitor->self == NULL ||
139 g_file_monitor_is_cancelled (monitor->self) ||
140 monitor->file_notify_buffer == NULL)
141 {
142 g_free (monitor->file_notify_buffer);
143 g_free (monitor);
144 return;
145 }
146
147 offset = 0;
148
149 do
150 {
151 pfile_notify_walker = (PFILE_NOTIFY_INFORMATION)((BYTE *)monitor->file_notify_buffer + offset);
152 if (pfile_notify_walker->Action > 0)
153 {
154 glong file_name_len;
155 gchar *changed_file;
156
157 changed_file = g_utf16_to_utf8 (pfile_notify_walker->FileName,
158 pfile_notify_walker->FileNameLength / sizeof(WCHAR),
159 NULL, &file_name_len, NULL);
160
161 if (monitor->isfile)
162 {
163 gint long_filename_length = wcslen (monitor->wfilename_long);
164 gint short_filename_length = wcslen (monitor->wfilename_short);
165 enum GWin32FileMonitorFileAlias alias_state;
166
167 /* If monitoring a file, check that the changed file
168 * in the directory matches the file that is to be monitored
169 * We need to check both the long and short file names for the same file.
170 *
171 * We need to send in the name of the monitored file, not its long (or short) variant,
172 * if they exist.
173 */
174
175 if (_wcsnicmp (pfile_notify_walker->FileName,
176 monitor->wfilename_long,
177 long_filename_length) == 0)
178 {
179 if (_wcsnicmp (pfile_notify_walker->FileName,
180 monitor->wfilename_short,
181 short_filename_length) == 0)
182 {
183 alias_state = G_WIN32_FILE_MONITOR_NO_ALIAS;
184 }
185 else
186 alias_state = G_WIN32_FILE_MONITOR_LONG_FILENAME;
187 }
188 else if (_wcsnicmp (pfile_notify_walker->FileName,
189 monitor->wfilename_short,
190 short_filename_length) == 0)
191 {
192 alias_state = G_WIN32_FILE_MONITOR_SHORT_FILENAME;
193 }
194 else
195 alias_state = G_WIN32_FILE_MONITOR_NO_MATCH_FOUND;
196
197 if (alias_state != G_WIN32_FILE_MONITOR_NO_MATCH_FOUND)
198 {
199 wchar_t *monitored_file_w;
200 gchar *monitored_file;
201
202 switch (alias_state)
203 {
204 case G_WIN32_FILE_MONITOR_NO_ALIAS:
205 monitored_file = g_strdup (changed_file);
206 break;
207 case G_WIN32_FILE_MONITOR_LONG_FILENAME:
208 case G_WIN32_FILE_MONITOR_SHORT_FILENAME:
209 monitored_file_w = wcsrchr (monitor->wfullpath_with_long_prefix, L'\\');
210 monitored_file = g_utf16_to_utf8 (monitored_file_w + 1, -1, NULL, NULL, NULL);
211 break;
212 default:
213 g_assert_not_reached ();
214 break;
215 }
216
217 g_win32_fs_monitor_handle_event (monitor, monitored_file, pfile_notify_walker);
218 g_free (monitored_file);
219 }
220 }
221 else
222 g_win32_fs_monitor_handle_event (monitor, changed_file, pfile_notify_walker);
223
224 g_free (changed_file);
225 }
226
227 monitor->pfni_prev = pfile_notify_walker;
228 offset += pfile_notify_walker->NextEntryOffset;
229 }
230 while (pfile_notify_walker->NextEntryOffset);
231
232 ReadDirectoryChangesW (monitor->hDirectory,
233 monitor->file_notify_buffer,
234 monitor->buffer_allocated_bytes,
235 FALSE,
236 notify_filter,
237 &monitor->buffer_filled_bytes,
238 &monitor->overlapped,
239 g_win32_fs_monitor_callback);
240 }
241
242 void
243 g_win32_fs_monitor_init (GWin32FSMonitorPrivate *monitor,
244 const gchar *dirname,
245 const gchar *filename,
246 gboolean isfile)
247 {
248 wchar_t *wdirname_with_long_prefix = NULL;
249 const gchar LONGPFX[] = "\\\\?\\";
250 gchar *fullpath_with_long_prefix, *dirname_with_long_prefix;
251 DWORD notify_filter = isfile ?
252 (FILE_NOTIFY_CHANGE_FILE_NAME |
253 FILE_NOTIFY_CHANGE_ATTRIBUTES |
254 FILE_NOTIFY_CHANGE_SIZE) :
255 (FILE_NOTIFY_CHANGE_FILE_NAME |
256 FILE_NOTIFY_CHANGE_DIR_NAME |
257 FILE_NOTIFY_CHANGE_ATTRIBUTES |
258 FILE_NOTIFY_CHANGE_SIZE);
259
260 gboolean success_attribs;
261 WIN32_FILE_ATTRIBUTE_DATA attrib_data = {0, };
262
263
264 if (dirname != NULL)
265 {
266 dirname_with_long_prefix = g_strconcat (LONGPFX, dirname, NULL);
267 wdirname_with_long_prefix = g_utf8_to_utf16 (dirname_with_long_prefix, -1, NULL, NULL, NULL);
268
269 if (isfile)
270 {
271 gchar *fullpath;
272 wchar_t wlongname[MAX_PATH_LONG];
273 wchar_t wshortname[MAX_PATH_LONG];
274 wchar_t *wfullpath, *wbasename_long, *wbasename_short;
275
276 fullpath = g_build_filename (dirname, filename, NULL);
277 fullpath_with_long_prefix = g_strconcat (LONGPFX, fullpath, NULL);
278
279 wfullpath = g_utf8_to_utf16 (fullpath, -1, NULL, NULL, NULL);
280
281 monitor->wfullpath_with_long_prefix =
282 g_utf8_to_utf16 (fullpath_with_long_prefix, -1, NULL, NULL, NULL);
283
284 /* ReadDirectoryChangesW() can return the normal filename or the
285 * "8.3" format filename, so we need to keep track of both these names
286 * so that we can check against them later when it returns
287 */
288 if (GetLongPathNameW (monitor->wfullpath_with_long_prefix, wlongname, MAX_PATH_LONG) == 0)
289 {
290 wbasename_long = wcsrchr (monitor->wfullpath_with_long_prefix, L'\\');
291 monitor->wfilename_long = wbasename_long != NULL ?
292 wcsdup (wbasename_long + 1) :
293 wcsdup (wfullpath);
294 }
295 else
296 {
297 wbasename_long = wcsrchr (wlongname, L'\\');
298 monitor->wfilename_long = wbasename_long != NULL ?
299 wcsdup (wbasename_long + 1) :
300 wcsdup (wlongname);
301
302 }
303
304 if (GetShortPathNameW (monitor->wfullpath_with_long_prefix, wshortname, MAX_PATH_LONG) == 0)
305 {
306 wbasename_short = wcsrchr (monitor->wfullpath_with_long_prefix, L'\\');
307 monitor->wfilename_short = wbasename_short != NULL ?
308 wcsdup (wbasename_short + 1) :
309 wcsdup (wfullpath);
310 }
311 else
312 {
313 wbasename_short = wcsrchr (wshortname, L'\\');
314 monitor->wfilename_short = wbasename_short != NULL ?
315 wcsdup (wbasename_short + 1) :
316 wcsdup (wshortname);
317 }
318
319 g_free (wfullpath);
320 g_free (fullpath);
321 }
322 else
323 {
324 monitor->wfilename_short = NULL;
325 monitor->wfilename_long = NULL;
326 monitor->wfullpath_with_long_prefix = g_utf8_to_utf16 (dirname_with_long_prefix, -1, NULL, NULL, NULL);
327 }
328
329 monitor->isfile = isfile;
330 }
331 else
332 {
333 dirname_with_long_prefix = g_strconcat (LONGPFX, filename, NULL);
334 monitor->wfullpath_with_long_prefix = g_utf8_to_utf16 (dirname_with_long_prefix, -1, NULL, NULL, NULL);
335 monitor->wfilename_long = NULL;
336 monitor->wfilename_short = NULL;
337 monitor->isfile = FALSE;
338 }
339
340 success_attribs = GetFileAttributesExW (monitor->wfullpath_with_long_prefix,
341 GetFileExInfoStandard,
342 &attrib_data);
343 if (success_attribs)
344 monitor->file_attribs = attrib_data.dwFileAttributes; /* Store up original attributes */
345 else
346 monitor->file_attribs = INVALID_FILE_ATTRIBUTES;
347 monitor->pfni_prev = NULL;
348 monitor->hDirectory = CreateFileW (wdirname_with_long_prefix != NULL ? wdirname_with_long_prefix : monitor->wfullpath_with_long_prefix,
349 FILE_LIST_DIRECTORY,
350 FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
351 NULL,
352 OPEN_EXISTING,
353 FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
354 NULL);
355
356 g_free (wdirname_with_long_prefix);
357 g_free (dirname_with_long_prefix);
358
359 if (monitor->hDirectory != INVALID_HANDLE_VALUE)
360 {
361 ReadDirectoryChangesW (monitor->hDirectory,
362 monitor->file_notify_buffer,
363 monitor->buffer_allocated_bytes,
364 FALSE,
365 notify_filter,
366 &monitor->buffer_filled_bytes,
367 &monitor->overlapped,
368 g_win32_fs_monitor_callback);
369 }
370 }
371
372 GWin32FSMonitorPrivate *
373 g_win32_fs_monitor_create (gboolean isfile)
374 {
375 GWin32FSMonitorPrivate *monitor = g_new0 (GWin32FSMonitorPrivate, 1);
376
377 monitor->buffer_allocated_bytes = 32784;
378 monitor->file_notify_buffer = g_new0 (FILE_NOTIFY_INFORMATION, monitor->buffer_allocated_bytes);
379
380 return monitor;
381 }
382
383 void
384 g_win32_fs_monitor_finalize (GWin32FSMonitorPrivate *monitor)
385 {
386 g_free (monitor->wfullpath_with_long_prefix);
387 g_free (monitor->wfilename_long);
388 g_free (monitor->wfilename_short);
389
390 if (monitor->hDirectory == INVALID_HANDLE_VALUE)
391 {
392 /* If we don't have a directory handle we can free
393 * monitor->file_notify_buffer and monitor here. The
394 * callback won't be called obviously any more (and presumably
395 * never has been called).
396 */
397 g_free (monitor->file_notify_buffer);
398 monitor->file_notify_buffer = NULL;
399 g_free (monitor);
400 }
401 else
402 {
403 /* If we have a directory handle, the OVERLAPPED struct is
404 * passed once more to the callback as a result of the
405 * CloseHandle() done in the cancel method, so monitor has to
406 * be kept around. The GWin32DirectoryMonitor object is
407 * disappearing, so can't leave a pointer to it in
408 * monitor->self.
409 */
410 monitor->self = NULL;
411 }
412 }
413
414 void
415 g_win32_fs_monitor_close_handle (GWin32FSMonitorPrivate *monitor)
416 {
417 /* This triggers a last callback() with nBytes==0. */
418
419 /* Actually I am not so sure about that, it seems to trigger a last
420 * callback correctly, but the way to recognize that it is the final
421 * one is not to check for nBytes==0, I think that was a
422 * misunderstanding.
423 */
424 if (monitor->hDirectory != INVALID_HANDLE_VALUE)
425 CloseHandle (monitor->hDirectory);
426 }