1 /* GIO - GLib Input, Output and Streaming Library
3 * Copyright (C) 2006-2007 Red Hat, Inc.
4 * Copyright (C) 2015 Chun-wei Fan
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 of the License, or (at your option) any later version.
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.
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/>.
19 * Author: Vlad Grecescu <b100dian@gmail.com>
20 * Author: Chun-wei Fan <fanc999@yahoo.com.tw>
26 #include "gwin32fsmonitorutils.h"
27 #include "gio/gfile.h"
31 #define MAX_PATH_LONG 32767 /* Support Paths longer than MAX_PATH (260) characters */
34 g_win32_fs_monitor_handle_event (GWin32FSMonitorPrivate *monitor,
36 PFILE_NOTIFY_INFORMATION pfni)
38 GFileMonitorEvent fme;
39 PFILE_NOTIFY_INFORMATION pfni_next;
40 WIN32_FILE_ATTRIBUTE_DATA attrib_data = {0, };
41 gchar *renamed_file = NULL;
45 case FILE_ACTION_ADDED:
46 fme = G_FILE_MONITOR_EVENT_CREATED;
49 case FILE_ACTION_REMOVED:
50 fme = G_FILE_MONITOR_EVENT_DELETED;
53 case FILE_ACTION_MODIFIED:
55 gboolean success_attribs = GetFileAttributesExW (monitor->wfullpath_with_long_prefix,
56 GetFileExInfoStandard,
59 if (monitor->file_attribs != INVALID_FILE_ATTRIBUTES &&
61 attrib_data.dwFileAttributes != monitor->file_attribs)
62 fme = G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED;
64 fme = G_FILE_MONITOR_EVENT_CHANGED;
66 monitor->file_attribs = attrib_data.dwFileAttributes;
70 case FILE_ACTION_RENAMED_OLD_NAME:
71 if (pfni->NextEntryOffset != 0)
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
77 glong file_name_len = 0;
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;
84 fme = G_FILE_MONITOR_EVENT_MOVED_OUT;
87 fme = G_FILE_MONITOR_EVENT_MOVED_OUT;
90 case FILE_ACTION_RENAMED_NEW_NAME:
91 if (monitor->pfni_prev != NULL &&
92 monitor->pfni_prev->Action == FILE_ACTION_RENAMED_OLD_NAME)
94 /* don't bother sending events, was already sent (rename) */
98 fme = G_FILE_MONITOR_EVENT_MOVED_IN;
102 /* The possible Windows actions are all above, so shouldn't get here */
103 g_assert_not_reached ();
107 return g_file_monitor_source_handle_event (monitor->fms,
112 g_get_monotonic_time ());
119 g_win32_fs_monitor_callback (DWORD error,
121 LPOVERLAPPED lpOverlapped)
124 PFILE_NOTIFY_INFORMATION pfile_notify_walker;
125 GWin32FSMonitorPrivate *monitor = (GWin32FSMonitorPrivate *) lpOverlapped;
127 DWORD notify_filter = monitor->isfile ?
128 (FILE_NOTIFY_CHANGE_FILE_NAME |
129 FILE_NOTIFY_CHANGE_ATTRIBUTES |
130 FILE_NOTIFY_CHANGE_SIZE) :
131 (FILE_NOTIFY_CHANGE_FILE_NAME |
132 FILE_NOTIFY_CHANGE_DIR_NAME |
133 FILE_NOTIFY_CHANGE_ATTRIBUTES |
134 FILE_NOTIFY_CHANGE_SIZE);
136 /* If monitor->self is NULL the GWin32FileMonitor object has been destroyed. */
137 if (monitor->self == NULL ||
138 g_file_monitor_is_cancelled (monitor->self) ||
139 monitor->file_notify_buffer == NULL)
141 g_free (monitor->file_notify_buffer);
150 pfile_notify_walker = (PFILE_NOTIFY_INFORMATION)((BYTE*)monitor->file_notify_buffer + offset);
151 if (pfile_notify_walker->Action > 0)
156 changed_file = g_utf16_to_utf8 (pfile_notify_walker->FileName, pfile_notify_walker->FileNameLength / sizeof(WCHAR), NULL, &file_name_len, NULL);
160 gint long_filename_length = wcslen (monitor->wfilename_long);
161 gint short_filename_length = wcslen (monitor->wfilename_short);
162 enum GWin32FileMonitorFileAlias alias_state;
164 /* If monitoring a file, check that the changed file
165 * in the directory matches the file that is to be monitored
166 * We need to check both the long and short file names for the same file.
168 * We need to send in the name of the monitored file, not its long (or short) variant,
172 if (_wcsnicmp (pfile_notify_walker->FileName,
173 monitor->wfilename_long,
174 long_filename_length) == 0)
176 if (_wcsnicmp (pfile_notify_walker->FileName,
177 monitor->wfilename_short,
178 short_filename_length) == 0)
180 alias_state = G_WIN32_FILE_MONITOR_NO_ALIAS;
183 alias_state = G_WIN32_FILE_MONITOR_LONG_FILENAME;
185 else if (_wcsnicmp (pfile_notify_walker->FileName,
186 monitor->wfilename_short,
187 short_filename_length) == 0)
189 alias_state = G_WIN32_FILE_MONITOR_SHORT_FILENAME;
192 alias_state = G_WIN32_FILE_MONITOR_NO_MATCH_FOUND;
194 if (alias_state != G_WIN32_FILE_MONITOR_NO_MATCH_FOUND)
196 wchar_t *monitored_file_w;
197 gchar *monitored_file;
201 case G_WIN32_FILE_MONITOR_NO_ALIAS:
202 monitored_file = g_strdup (changed_file);
204 case G_WIN32_FILE_MONITOR_LONG_FILENAME:
205 case G_WIN32_FILE_MONITOR_SHORT_FILENAME:
206 monitored_file_w = wcsrchr (monitor->wfullpath_with_long_prefix, L'\\');
207 monitored_file = g_utf16_to_utf8 (monitored_file_w + 1, -1, NULL, NULL, NULL);
210 g_assert_not_reached ();
214 g_win32_fs_monitor_handle_event (monitor, monitored_file, pfile_notify_walker);
215 g_free (monitored_file);
219 g_win32_fs_monitor_handle_event (monitor, changed_file, pfile_notify_walker);
221 g_free (changed_file);
223 monitor->pfni_prev = pfile_notify_walker;
224 offset += pfile_notify_walker->NextEntryOffset;
226 while (pfile_notify_walker->NextEntryOffset);
228 ReadDirectoryChangesW (monitor->hDirectory,
229 monitor->file_notify_buffer,
230 monitor->buffer_allocated_bytes,
233 &monitor->buffer_filled_bytes,
234 &monitor->overlapped,
235 g_win32_fs_monitor_callback);
239 g_win32_fs_monitor_init (GWin32FSMonitorPrivate *monitor,
244 wchar_t *wdirname_with_long_prefix = NULL;
245 const gchar LONGPFX[] = "\\\\?\\";
246 gchar *fullpath_with_long_prefix, *dirname_with_long_prefix;
247 DWORD notify_filter = isfile ?
248 (FILE_NOTIFY_CHANGE_FILE_NAME |
249 FILE_NOTIFY_CHANGE_ATTRIBUTES |
250 FILE_NOTIFY_CHANGE_SIZE) :
251 (FILE_NOTIFY_CHANGE_FILE_NAME |
252 FILE_NOTIFY_CHANGE_DIR_NAME |
253 FILE_NOTIFY_CHANGE_ATTRIBUTES |
254 FILE_NOTIFY_CHANGE_SIZE);
256 gboolean success_attribs;
257 WIN32_FILE_ATTRIBUTE_DATA attrib_data = {0, };
262 dirname_with_long_prefix = g_strconcat (LONGPFX, dirname, NULL);
263 wdirname_with_long_prefix = g_utf8_to_utf16 (dirname_with_long_prefix, -1, NULL, NULL, NULL);
268 wchar_t wlongname[MAX_PATH_LONG];
269 wchar_t wshortname[MAX_PATH_LONG];
270 wchar_t *wfullpath, *wbasename_long, *wbasename_short;
272 fullpath = g_build_filename (dirname, filename, NULL);
273 fullpath_with_long_prefix = g_strconcat (LONGPFX, fullpath, NULL);
275 wfullpath = g_utf8_to_utf16 (fullpath, -1, NULL, NULL, NULL);
277 monitor->wfullpath_with_long_prefix =
278 g_utf8_to_utf16 (fullpath_with_long_prefix, -1, NULL, NULL, NULL);
280 /* ReadDirectoryChangesW() can return the normal filename or the
281 * "8.3" format filename, so we need to keep track of both these names
282 * so that we can check against them later when it returns
284 if (GetLongPathNameW (monitor->wfullpath_with_long_prefix, wlongname, MAX_PATH_LONG) == 0)
286 wbasename_long = wcsrchr (monitor->wfullpath_with_long_prefix, L'\\');
287 monitor->wfilename_long = wbasename_long != NULL ?
288 wcsdup (wbasename_long + 1) :
293 wbasename_long = wcsrchr (wlongname, L'\\');
294 monitor->wfilename_long = wbasename_long != NULL ?
295 wcsdup (wbasename_long + 1) :
300 if (GetShortPathNameW (monitor->wfullpath_with_long_prefix, wshortname, MAX_PATH_LONG) == 0)
302 wbasename_short = wcsrchr (monitor->wfullpath_with_long_prefix, L'\\');
303 monitor->wfilename_short = wbasename_short != NULL ?
304 wcsdup (wbasename_short + 1) :
309 wbasename_short = wcsrchr (wshortname, L'\\');
310 monitor->wfilename_short = wbasename_short != NULL ?
311 wcsdup (wbasename_short + 1) :
319 monitor->wfilename_short = NULL;
320 monitor->wfilename_long = NULL;
321 monitor->wfullpath_with_long_prefix = g_utf8_to_utf16 (dirname_with_long_prefix, -1, NULL, NULL, NULL);
324 monitor->isfile = isfile;
328 dirname_with_long_prefix = g_strconcat (LONGPFX, filename, NULL);
329 monitor->wfullpath_with_long_prefix = g_utf8_to_utf16 (dirname_with_long_prefix, -1, NULL, NULL, NULL);
330 monitor->wfilename_long = NULL;
331 monitor->wfilename_short = NULL;
332 monitor->isfile = FALSE;
335 success_attribs = GetFileAttributesExW (monitor->wfullpath_with_long_prefix,
336 GetFileExInfoStandard,
339 monitor->file_attribs = attrib_data.dwFileAttributes; /* Store up original attributes */
341 monitor->file_attribs = INVALID_FILE_ATTRIBUTES;
342 monitor->pfni_prev = NULL;
343 monitor->hDirectory = CreateFileW (wdirname_with_long_prefix != NULL ? wdirname_with_long_prefix : monitor->wfullpath_with_long_prefix,
344 FILE_GENERIC_READ | FILE_GENERIC_WRITE,
345 FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
348 FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
351 if (wdirname_with_long_prefix != NULL)
352 g_free (wdirname_with_long_prefix);
353 g_free (dirname_with_long_prefix);
355 if (monitor->hDirectory != INVALID_HANDLE_VALUE)
357 ReadDirectoryChangesW (monitor->hDirectory,
358 monitor->file_notify_buffer,
359 monitor->buffer_allocated_bytes,
362 &monitor->buffer_filled_bytes,
363 &monitor->overlapped,
364 g_win32_fs_monitor_callback);
368 GWin32FSMonitorPrivate* g_win32_fs_monitor_create (gboolean isfile)
370 GWin32FSMonitorPrivate* monitor = (GWin32FSMonitorPrivate*) g_new0 (GWin32FSMonitorPrivate, 1);
371 g_assert (monitor != 0);
373 monitor->buffer_allocated_bytes = 32784;
374 monitor->file_notify_buffer = g_new0 (FILE_NOTIFY_INFORMATION, monitor->buffer_allocated_bytes);
375 g_assert (monitor->file_notify_buffer);
380 void g_win32_fs_monitor_finalize (GWin32FSMonitorPrivate *monitor)
382 if (monitor->hDirectory == INVALID_HANDLE_VALUE)
384 /* If we don't have a directory handle we can free
385 * monitor->file_notify_buffer and monitor here. The
386 * callback won't be called obviously any more (and presumably
387 * never has been called).
389 g_free (monitor->file_notify_buffer);
390 monitor->file_notify_buffer = NULL;
395 /* If we have a directory handle, the OVERLAPPED struct is
396 * passed once more to the callback as a result of the
397 * CloseHandle() done in the cancel method, so monitor has to
398 * be kept around. The GWin32DirectoryMonitor object is
399 * disappearing, so can't leave a pointer to it in
402 monitor->self = NULL;
404 g_free (monitor->wfullpath_with_long_prefix);
405 if (monitor->wfilename_long != NULL)
406 g_free (monitor->wfilename_long);
407 if (monitor->wfilename_short != NULL)
408 g_free (monitor->wfilename_short);
411 void g_win32_fs_monitor_close_handle (GWin32FSMonitorPrivate *monitor)
413 /* This triggers a last callback() with nBytes==0. */
415 /* Actually I am not so sure about that, it seems to trigger a last
416 * callback allright, but the way to recognize that it is the final
417 * one is not to check for nBytes==0, I think that was a
420 if (monitor->hDirectory != INVALID_HANDLE_VALUE)
421 CloseHandle (monitor->hDirectory);