Merge branch 'test-ip_mreq_source-android-only' into 'master'
[glib.git] / gio / win32 / gwin32fsmonitorutils.c
blobff8d1710cc63e30feed194b4903ffbc2bd9cda18
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.1 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>
24 #include "config.h"
26 #include "gwin32fsmonitorutils.h"
27 #include "gio/gfile.h"
29 #include <windows.h>
31 #define MAX_PATH_LONG 32767 /* Support Paths longer than MAX_PATH (260) characters */
33 static gboolean
34 g_win32_fs_monitor_handle_event (GWin32FSMonitorPrivate *monitor,
35 const gchar *filename,
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;
43 switch (pfni->Action)
45 case FILE_ACTION_ADDED:
46 fme = G_FILE_MONITOR_EVENT_CREATED;
47 break;
49 case FILE_ACTION_REMOVED:
50 fme = G_FILE_MONITOR_EVENT_DELETED;
51 break;
53 case FILE_ACTION_MODIFIED:
55 gboolean success_attribs = GetFileAttributesExW (monitor->wfullpath_with_long_prefix,
56 GetFileExInfoStandard,
57 &attrib_data);
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;
66 monitor->file_attribs = attrib_data.dwFileAttributes;
68 break;
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
75 * structure.
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;
83 else
84 fme = G_FILE_MONITOR_EVENT_MOVED_OUT;
86 else
87 fme = G_FILE_MONITOR_EVENT_MOVED_OUT;
88 break;
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) */
95 fme = -1;
97 else
98 fme = G_FILE_MONITOR_EVENT_MOVED_IN;
99 break;
101 default:
102 /* The possible Windows actions are all above, so shouldn't get here */
103 g_assert_not_reached ();
104 break;
107 if (fme != -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;
119 static void CALLBACK
120 g_win32_fs_monitor_callback (DWORD error,
121 DWORD nBytes,
122 LPOVERLAPPED lpOverlapped)
124 gulong offset;
125 PFILE_NOTIFY_INFORMATION pfile_notify_walker;
126 GWin32FSMonitorPrivate *monitor = (GWin32FSMonitorPrivate *) lpOverlapped;
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);
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)
142 g_free (monitor->file_notify_buffer);
143 g_free (monitor);
144 return;
147 offset = 0;
151 pfile_notify_walker = (PFILE_NOTIFY_INFORMATION)((BYTE *)monitor->file_notify_buffer + offset);
152 if (pfile_notify_walker->Action > 0)
154 glong file_name_len;
155 gchar *changed_file;
157 changed_file = g_utf16_to_utf8 (pfile_notify_walker->FileName,
158 pfile_notify_walker->FileNameLength / sizeof(WCHAR),
159 NULL, &file_name_len, NULL);
161 if (monitor->isfile)
163 gint long_filename_length = wcslen (monitor->wfilename_long);
164 gint short_filename_length = wcslen (monitor->wfilename_short);
165 enum GWin32FileMonitorFileAlias alias_state;
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.
171 * We need to send in the name of the monitored file, not its long (or short) variant,
172 * if they exist.
175 if (_wcsnicmp (pfile_notify_walker->FileName,
176 monitor->wfilename_long,
177 long_filename_length) == 0)
179 if (_wcsnicmp (pfile_notify_walker->FileName,
180 monitor->wfilename_short,
181 short_filename_length) == 0)
183 alias_state = G_WIN32_FILE_MONITOR_NO_ALIAS;
185 else
186 alias_state = G_WIN32_FILE_MONITOR_LONG_FILENAME;
188 else if (_wcsnicmp (pfile_notify_walker->FileName,
189 monitor->wfilename_short,
190 short_filename_length) == 0)
192 alias_state = G_WIN32_FILE_MONITOR_SHORT_FILENAME;
194 else
195 alias_state = G_WIN32_FILE_MONITOR_NO_MATCH_FOUND;
197 if (alias_state != G_WIN32_FILE_MONITOR_NO_MATCH_FOUND)
199 wchar_t *monitored_file_w;
200 gchar *monitored_file;
202 switch (alias_state)
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;
217 g_win32_fs_monitor_handle_event (monitor, monitored_file, pfile_notify_walker);
218 g_free (monitored_file);
221 else
222 g_win32_fs_monitor_handle_event (monitor, changed_file, pfile_notify_walker);
224 g_free (changed_file);
227 monitor->pfni_prev = pfile_notify_walker;
228 offset += pfile_notify_walker->NextEntryOffset;
230 while (pfile_notify_walker->NextEntryOffset);
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);
242 void
243 g_win32_fs_monitor_init (GWin32FSMonitorPrivate *monitor,
244 const gchar *dirname,
245 const gchar *filename,
246 gboolean isfile)
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);
260 gboolean success_attribs;
261 WIN32_FILE_ATTRIBUTE_DATA attrib_data = {0, };
264 if (dirname != NULL)
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);
269 if (isfile)
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;
276 fullpath = g_build_filename (dirname, filename, NULL);
277 fullpath_with_long_prefix = g_strconcat (LONGPFX, fullpath, NULL);
279 wfullpath = g_utf8_to_utf16 (fullpath, -1, NULL, NULL, NULL);
281 monitor->wfullpath_with_long_prefix =
282 g_utf8_to_utf16 (fullpath_with_long_prefix, -1, NULL, NULL, NULL);
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
288 if (GetLongPathNameW (monitor->wfullpath_with_long_prefix, wlongname, MAX_PATH_LONG) == 0)
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);
295 else
297 wbasename_long = wcsrchr (wlongname, L'\\');
298 monitor->wfilename_long = wbasename_long != NULL ?
299 wcsdup (wbasename_long + 1) :
300 wcsdup (wlongname);
304 if (GetShortPathNameW (monitor->wfullpath_with_long_prefix, wshortname, MAX_PATH_LONG) == 0)
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);
311 else
313 wbasename_short = wcsrchr (wshortname, L'\\');
314 monitor->wfilename_short = wbasename_short != NULL ?
315 wcsdup (wbasename_short + 1) :
316 wcsdup (wshortname);
319 g_free (fullpath);
321 else
323 monitor->wfilename_short = NULL;
324 monitor->wfilename_long = NULL;
325 monitor->wfullpath_with_long_prefix = g_utf8_to_utf16 (dirname_with_long_prefix, -1, NULL, NULL, NULL);
328 monitor->isfile = isfile;
330 else
332 dirname_with_long_prefix = g_strconcat (LONGPFX, filename, NULL);
333 monitor->wfullpath_with_long_prefix = g_utf8_to_utf16 (dirname_with_long_prefix, -1, NULL, NULL, NULL);
334 monitor->wfilename_long = NULL;
335 monitor->wfilename_short = NULL;
336 monitor->isfile = FALSE;
339 success_attribs = GetFileAttributesExW (monitor->wfullpath_with_long_prefix,
340 GetFileExInfoStandard,
341 &attrib_data);
342 if (success_attribs)
343 monitor->file_attribs = attrib_data.dwFileAttributes; /* Store up original attributes */
344 else
345 monitor->file_attribs = INVALID_FILE_ATTRIBUTES;
346 monitor->pfni_prev = NULL;
347 monitor->hDirectory = CreateFileW (wdirname_with_long_prefix != NULL ? wdirname_with_long_prefix : monitor->wfullpath_with_long_prefix,
348 FILE_GENERIC_READ | FILE_GENERIC_WRITE,
349 FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
350 NULL,
351 OPEN_EXISTING,
352 FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
353 NULL);
355 g_free (wdirname_with_long_prefix);
356 g_free (dirname_with_long_prefix);
358 if (monitor->hDirectory != INVALID_HANDLE_VALUE)
360 ReadDirectoryChangesW (monitor->hDirectory,
361 monitor->file_notify_buffer,
362 monitor->buffer_allocated_bytes,
363 FALSE,
364 notify_filter,
365 &monitor->buffer_filled_bytes,
366 &monitor->overlapped,
367 g_win32_fs_monitor_callback);
371 GWin32FSMonitorPrivate *
372 g_win32_fs_monitor_create (gboolean isfile)
374 GWin32FSMonitorPrivate *monitor = g_new0 (GWin32FSMonitorPrivate, 1);
376 monitor->buffer_allocated_bytes = 32784;
377 monitor->file_notify_buffer = g_new0 (FILE_NOTIFY_INFORMATION, monitor->buffer_allocated_bytes);
379 return monitor;
382 void
383 g_win32_fs_monitor_finalize (GWin32FSMonitorPrivate *monitor)
385 g_free (monitor->wfullpath_with_long_prefix);
386 g_free (monitor->wfilename_long);
387 g_free (monitor->wfilename_short);
389 if (monitor->hDirectory == INVALID_HANDLE_VALUE)
391 /* If we don't have a directory handle we can free
392 * monitor->file_notify_buffer and monitor here. The
393 * callback won't be called obviously any more (and presumably
394 * never has been called).
396 g_free (monitor->file_notify_buffer);
397 monitor->file_notify_buffer = NULL;
398 g_free (monitor);
400 else
402 /* If we have a directory handle, the OVERLAPPED struct is
403 * passed once more to the callback as a result of the
404 * CloseHandle() done in the cancel method, so monitor has to
405 * be kept around. The GWin32DirectoryMonitor object is
406 * disappearing, so can't leave a pointer to it in
407 * monitor->self.
409 monitor->self = NULL;
413 void
414 g_win32_fs_monitor_close_handle (GWin32FSMonitorPrivate *monitor)
416 /* This triggers a last callback() with nBytes==0. */
418 /* Actually I am not so sure about that, it seems to trigger a last
419 * callback allright, but the way to recognize that it is the final
420 * one is not to check for nBytes==0, I think that was a
421 * misunderstanding.
423 if (monitor->hDirectory != INVALID_HANDLE_VALUE)
424 CloseHandle (monitor->hDirectory);