Win32: Port Directory Monitoring to New GLocalFileMonitor
[platform/upstream/glib.git] / gio / win32 / gwin32fsmonitorutils.c
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 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                                  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   if (fme != -1)
107     return g_file_monitor_source_handle_event (monitor->fms,
108                                                fme,
109                                                filename,
110                                                renamed_file,
111                                                NULL,
112                                                g_get_monotonic_time ());
113   else
114     return FALSE;
115 }
116
117
118 static void CALLBACK
119 g_win32_fs_monitor_callback (DWORD        error,
120                              DWORD        nBytes,
121                              LPOVERLAPPED lpOverlapped)
122 {
123   gulong offset;
124   PFILE_NOTIFY_INFORMATION pfile_notify_walker;
125   GWin32FSMonitorPrivate *monitor = (GWin32FSMonitorPrivate *) lpOverlapped;
126
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);
135
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)
140     {
141       g_free (monitor->file_notify_buffer);
142       g_free (monitor);
143       return;
144     }
145
146   offset = 0;
147
148   do
149     {
150       pfile_notify_walker = (PFILE_NOTIFY_INFORMATION)((BYTE*)monitor->file_notify_buffer + offset);
151       if (pfile_notify_walker->Action > 0)
152         {
153           glong file_name_len;
154           gchar *changed_file;
155
156           changed_file = g_utf16_to_utf8 (pfile_notify_walker->FileName, pfile_notify_walker->FileNameLength / sizeof(WCHAR), NULL, &file_name_len, NULL);
157
158           if (monitor->isfile)
159             {
160               gint long_filename_length = wcslen (monitor->wfilename_long);
161               gint short_filename_length = wcslen (monitor->wfilename_short);
162               enum GWin32FileMonitorFileAlias alias_state;
163
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.
167               *
168               * We need to send in the name of the monitored file, not its long (or short) variant,
169               * if they exist.
170               */
171
172               if (_wcsnicmp (pfile_notify_walker->FileName,
173                              monitor->wfilename_long,
174                              long_filename_length) == 0)
175                 {
176                   if (_wcsnicmp (pfile_notify_walker->FileName,
177                                  monitor->wfilename_short,
178                                  short_filename_length) == 0)
179                     {
180                       alias_state = G_WIN32_FILE_MONITOR_NO_ALIAS;
181                     }
182                   else
183                     alias_state = G_WIN32_FILE_MONITOR_LONG_FILENAME;
184                 }
185               else if (_wcsnicmp (pfile_notify_walker->FileName,
186                                   monitor->wfilename_short,
187                                   short_filename_length) == 0)
188                 {
189                   alias_state = G_WIN32_FILE_MONITOR_SHORT_FILENAME;
190                 }
191               else
192                 alias_state = G_WIN32_FILE_MONITOR_NO_MATCH_FOUND;
193
194               if (alias_state != G_WIN32_FILE_MONITOR_NO_MATCH_FOUND)
195                 {
196                   wchar_t *monitored_file_w;
197                   gchar *monitored_file;
198
199                   switch (alias_state)
200                     {
201                       case G_WIN32_FILE_MONITOR_NO_ALIAS:
202                         monitored_file = g_strdup (changed_file);
203                         break;
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);
208                         break;
209                       default:
210                         g_assert_not_reached ();
211                         break;
212                     }
213
214                   g_win32_fs_monitor_handle_event (monitor, monitored_file, pfile_notify_walker);
215                   g_free (monitored_file);
216                 }
217             }
218           else
219             g_win32_fs_monitor_handle_event (monitor, changed_file, pfile_notify_walker);
220
221           g_free (changed_file);
222         }
223       monitor->pfni_prev = pfile_notify_walker;
224       offset += pfile_notify_walker->NextEntryOffset;
225     }
226   while (pfile_notify_walker->NextEntryOffset);
227
228   ReadDirectoryChangesW (monitor->hDirectory,
229                          monitor->file_notify_buffer,
230                          monitor->buffer_allocated_bytes,
231                          FALSE,
232                          notify_filter,
233                          &monitor->buffer_filled_bytes,
234                          &monitor->overlapped,
235                          g_win32_fs_monitor_callback);
236 }
237
238 void
239 g_win32_fs_monitor_init (GWin32FSMonitorPrivate *monitor,
240                          gchar *dirname,
241                          gchar *filename,
242                          gboolean isfile)
243 {
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);
255
256   gboolean success_attribs;
257   WIN32_FILE_ATTRIBUTE_DATA attrib_data = {0, };
258
259
260   if (dirname != NULL)
261     {
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);
264
265       if (isfile)
266         {
267           gchar *fullpath;
268           wchar_t wlongname[MAX_PATH_LONG];
269           wchar_t wshortname[MAX_PATH_LONG];
270           wchar_t *wfullpath, *wbasename_long, *wbasename_short;
271
272           fullpath = g_build_filename (dirname, filename, NULL);
273           fullpath_with_long_prefix = g_strconcat (LONGPFX, fullpath, NULL);
274
275           wfullpath = g_utf8_to_utf16 (fullpath, -1, NULL, NULL, NULL);
276
277           monitor->wfullpath_with_long_prefix =
278             g_utf8_to_utf16 (fullpath_with_long_prefix, -1, NULL, NULL, NULL);
279
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
283            */
284           if (GetLongPathNameW (monitor->wfullpath_with_long_prefix, wlongname, MAX_PATH_LONG) == 0)
285             {
286               wbasename_long = wcsrchr (monitor->wfullpath_with_long_prefix, L'\\');
287               monitor->wfilename_long = wbasename_long != NULL ?
288                                         wcsdup (wbasename_long + 1) :
289                                         wcsdup (wfullpath);
290             }
291           else
292             {
293               wbasename_long = wcsrchr (wlongname, L'\\');
294               monitor->wfilename_long = wbasename_long != NULL ?
295                                         wcsdup (wbasename_long + 1) :
296                                         wcsdup (wlongname);
297
298             }
299
300           if (GetShortPathNameW (monitor->wfullpath_with_long_prefix, wshortname, MAX_PATH_LONG) == 0)
301             {
302               wbasename_short = wcsrchr (monitor->wfullpath_with_long_prefix, L'\\');
303               monitor->wfilename_short = wbasename_short != NULL ?
304                                          wcsdup (wbasename_short + 1) :
305                                          wcsdup (wfullpath);
306             }
307           else
308             {
309               wbasename_short = wcsrchr (wshortname, L'\\');
310               monitor->wfilename_short = wbasename_short != NULL ?
311                                          wcsdup (wbasename_short + 1) :
312                                          wcsdup (wshortname);
313             }
314
315           g_free (fullpath);
316         }
317       else
318         {
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);
322         }
323
324       monitor->isfile = isfile;
325     }
326   else
327     {
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;
333     }
334
335   success_attribs = GetFileAttributesExW (monitor->wfullpath_with_long_prefix,
336                                           GetFileExInfoStandard,
337                                           &attrib_data);
338   if (success_attribs)
339     monitor->file_attribs = attrib_data.dwFileAttributes; /* Store up original attributes */
340   else
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,
346                                      NULL,
347                                      OPEN_EXISTING,
348                                      FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
349                                      NULL);
350
351   if (wdirname_with_long_prefix != NULL)
352     g_free (wdirname_with_long_prefix);
353   g_free (dirname_with_long_prefix);
354
355   if (monitor->hDirectory != INVALID_HANDLE_VALUE)
356     {
357       ReadDirectoryChangesW (monitor->hDirectory,
358                              monitor->file_notify_buffer,
359                              monitor->buffer_allocated_bytes,
360                              FALSE,
361                              notify_filter,
362                              &monitor->buffer_filled_bytes,
363                              &monitor->overlapped,
364                              g_win32_fs_monitor_callback);
365     }
366 }
367
368 GWin32FSMonitorPrivate* g_win32_fs_monitor_create (gboolean isfile)
369 {
370   GWin32FSMonitorPrivate* monitor = (GWin32FSMonitorPrivate*) g_new0 (GWin32FSMonitorPrivate, 1);
371   g_assert (monitor != 0);
372
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);
376
377   return monitor;
378 }
379
380 void g_win32_fs_monitor_finalize (GWin32FSMonitorPrivate *monitor)
381 {
382   if (monitor->hDirectory == INVALID_HANDLE_VALUE)
383     {
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).
388        */
389       g_free (monitor->file_notify_buffer);
390       monitor->file_notify_buffer = NULL;
391       g_free (monitor);
392     }
393   else
394     {
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
400        * monitor->self.
401        */
402       monitor->self = NULL;
403     }
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);
409 }
410
411 void g_win32_fs_monitor_close_handle (GWin32FSMonitorPrivate *monitor)
412 {
413   /* This triggers a last callback() with nBytes==0. */
414
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
418    * misunderstanding.
419    */
420   if (monitor->hDirectory != INVALID_HANDLE_VALUE)
421     CloseHandle (monitor->hDirectory);
422 }