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
g_win32_fs_monitor_handle_event(GWin32FSMonitorPrivate * monitor,const gchar * filename,PFILE_NOTIFY_INFORMATION pfni)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 = -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 != -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
g_win32_fs_monitor_callback(DWORD error,DWORD nBytes,LPOVERLAPPED lpOverlapped)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
g_win32_fs_monitor_init(GWin32FSMonitorPrivate * monitor,const gchar * dirname,const gchar * filename,gboolean isfile)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 (fullpath);
320 }
321 else
322 {
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);
326 }
327
328 monitor->isfile = isfile;
329 }
330 else
331 {
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;
337 }
338
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_LIST_DIRECTORY,
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);
354
355 g_free (wdirname_with_long_prefix);
356 g_free (dirname_with_long_prefix);
357
358 if (monitor->hDirectory != INVALID_HANDLE_VALUE)
359 {
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);
368 }
369 }
370
371 GWin32FSMonitorPrivate *
g_win32_fs_monitor_create(gboolean isfile)372 g_win32_fs_monitor_create (gboolean isfile)
373 {
374 GWin32FSMonitorPrivate *monitor = g_new0 (GWin32FSMonitorPrivate, 1);
375
376 monitor->buffer_allocated_bytes = 32784;
377 monitor->file_notify_buffer = g_new0 (FILE_NOTIFY_INFORMATION, monitor->buffer_allocated_bytes);
378
379 return monitor;
380 }
381
382 void
g_win32_fs_monitor_finalize(GWin32FSMonitorPrivate * monitor)383 g_win32_fs_monitor_finalize (GWin32FSMonitorPrivate *monitor)
384 {
385 g_free (monitor->wfullpath_with_long_prefix);
386 g_free (monitor->wfilename_long);
387 g_free (monitor->wfilename_short);
388
389 if (monitor->hDirectory == INVALID_HANDLE_VALUE)
390 {
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).
395 */
396 g_free (monitor->file_notify_buffer);
397 monitor->file_notify_buffer = NULL;
398 g_free (monitor);
399 }
400 else
401 {
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.
408 */
409 monitor->self = NULL;
410 }
411 }
412
413 void
g_win32_fs_monitor_close_handle(GWin32FSMonitorPrivate * monitor)414 g_win32_fs_monitor_close_handle (GWin32FSMonitorPrivate *monitor)
415 {
416 /* This triggers a last callback() with nBytes==0. */
417
418 /* Actually I am not so sure about that, it seems to trigger a last
419 * callback correctly, 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.
422 */
423 if (monitor->hDirectory != INVALID_HANDLE_VALUE)
424 CloseHandle (monitor->hDirectory);
425 }
426