(root)/
glib-2.79.0/
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.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
      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 = (GFileMonitorEvent) -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 != (GFileMonitorEvent) -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
     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
     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 (wfullpath);
     320            g_free (fullpath);
     321          }
     322        else
     323          {
     324            monitor->wfilename_short = NULL;
     325            monitor->wfilename_long = NULL;
     326            monitor->wfullpath_with_long_prefix = g_utf8_to_utf16 (dirname_with_long_prefix, -1, NULL, NULL, NULL);
     327          }
     328  
     329        monitor->isfile = isfile;
     330      }
     331    else
     332      {
     333        dirname_with_long_prefix = g_strconcat (LONGPFX, filename, NULL);
     334        monitor->wfullpath_with_long_prefix = g_utf8_to_utf16 (dirname_with_long_prefix, -1, NULL, NULL, NULL);
     335        monitor->wfilename_long = NULL;
     336        monitor->wfilename_short = NULL;
     337        monitor->isfile = FALSE;
     338      }
     339  
     340    success_attribs = GetFileAttributesExW (monitor->wfullpath_with_long_prefix,
     341                                            GetFileExInfoStandard,
     342                                            &attrib_data);
     343    if (success_attribs)
     344      monitor->file_attribs = attrib_data.dwFileAttributes; /* Store up original attributes */
     345    else
     346      monitor->file_attribs = INVALID_FILE_ATTRIBUTES;
     347    monitor->pfni_prev = NULL;
     348    monitor->hDirectory = CreateFileW (wdirname_with_long_prefix != NULL ? wdirname_with_long_prefix : monitor->wfullpath_with_long_prefix,
     349                                       FILE_LIST_DIRECTORY,
     350                                       FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
     351                                       NULL,
     352                                       OPEN_EXISTING,
     353                                       FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
     354                                       NULL);
     355  
     356    g_free (wdirname_with_long_prefix);
     357    g_free (dirname_with_long_prefix);
     358  
     359    if (monitor->hDirectory != INVALID_HANDLE_VALUE)
     360      {
     361        ReadDirectoryChangesW (monitor->hDirectory,
     362                               monitor->file_notify_buffer,
     363                               monitor->buffer_allocated_bytes,
     364                               FALSE,
     365                               notify_filter,
     366                               &monitor->buffer_filled_bytes,
     367                               &monitor->overlapped,
     368                               g_win32_fs_monitor_callback);
     369      }
     370  }
     371  
     372  GWin32FSMonitorPrivate *
     373  g_win32_fs_monitor_create (gboolean isfile)
     374  {
     375    GWin32FSMonitorPrivate *monitor = g_new0 (GWin32FSMonitorPrivate, 1);
     376  
     377    monitor->buffer_allocated_bytes = 32784;
     378    monitor->file_notify_buffer = g_new0 (FILE_NOTIFY_INFORMATION, monitor->buffer_allocated_bytes);
     379  
     380    return monitor;
     381  }
     382  
     383  void
     384  g_win32_fs_monitor_finalize (GWin32FSMonitorPrivate *monitor)
     385  {
     386    g_free (monitor->wfullpath_with_long_prefix);
     387    g_free (monitor->wfilename_long);
     388    g_free (monitor->wfilename_short);
     389  
     390    if (monitor->hDirectory == INVALID_HANDLE_VALUE)
     391      {
     392        /* If we don't have a directory handle we can free
     393         * monitor->file_notify_buffer and monitor here. The
     394         * callback won't be called obviously any more (and presumably
     395         * never has been called).
     396         */
     397        g_free (monitor->file_notify_buffer);
     398        monitor->file_notify_buffer = NULL;
     399        g_free (monitor);
     400      }
     401    else
     402      {
     403        /* If we have a directory handle, the OVERLAPPED struct is
     404         * passed once more to the callback as a result of the
     405         * CloseHandle() done in the cancel method, so monitor has to
     406         * be kept around. The GWin32DirectoryMonitor object is
     407         * disappearing, so can't leave a pointer to it in
     408         * monitor->self.
     409         */
     410        monitor->self = NULL;
     411      }
     412  }
     413  
     414  void
     415  g_win32_fs_monitor_close_handle (GWin32FSMonitorPrivate *monitor)
     416  {
     417    /* This triggers a last callback() with nBytes==0. */
     418  
     419    /* Actually I am not so sure about that, it seems to trigger a last
     420     * callback correctly, but the way to recognize that it is the final
     421     * one is not to check for nBytes==0, I think that was a
     422     * misunderstanding.
     423     */
     424    if (monitor->hDirectory != INVALID_HANDLE_VALUE)
     425      CloseHandle (monitor->hDirectory);
     426  }