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