Blame gio/win32/gwin32fsmonitorutils.c

Packit ae235b
/* GIO - GLib Input, Output and Streaming Library
Packit ae235b
 *
Packit ae235b
 * Copyright (C) 2006-2007 Red Hat, Inc.
Packit ae235b
 * Copyright (C) 2015 Chun-wei Fan
Packit ae235b
 *
Packit ae235b
 * This library is free software; you can redistribute it and/or
Packit ae235b
 * modify it under the terms of the GNU Lesser General Public
Packit ae235b
 * License as published by the Free Software Foundation; either
Packit ae235b
 * version 2.1 of the License, or (at your option) any later version.
Packit ae235b
 *
Packit ae235b
 * This library is distributed in the hope that it will be useful,
Packit ae235b
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit ae235b
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit ae235b
 * Lesser General Public License for more details.
Packit ae235b
 *
Packit ae235b
 * You should have received a copy of the GNU Lesser General
Packit ae235b
 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
Packit ae235b
 *
Packit ae235b
 * Author: Vlad Grecescu <b100dian@gmail.com>
Packit ae235b
 * Author: Chun-wei Fan <fanc999@yahoo.com.tw>
Packit ae235b
 *
Packit ae235b
 */
Packit ae235b
Packit ae235b
#include "config.h"
Packit ae235b
Packit ae235b
#include "gwin32fsmonitorutils.h"
Packit ae235b
#include "gio/gfile.h"
Packit ae235b
Packit ae235b
#include <windows.h>
Packit ae235b
Packit ae235b
#define MAX_PATH_LONG 32767 /* Support Paths longer than MAX_PATH (260) characters */
Packit ae235b
Packit ae235b
static gboolean
Packit ae235b
g_win32_fs_monitor_handle_event (GWin32FSMonitorPrivate   *monitor,
Packit ae235b
                                 const gchar              *filename,
Packit ae235b
                                 PFILE_NOTIFY_INFORMATION  pfni)
Packit ae235b
{
Packit ae235b
  GFileMonitorEvent fme;
Packit ae235b
  PFILE_NOTIFY_INFORMATION pfni_next;
Packit ae235b
  WIN32_FILE_ATTRIBUTE_DATA attrib_data = {0, };
Packit ae235b
  gchar *renamed_file = NULL;
Packit ae235b
Packit ae235b
  switch (pfni->Action)
Packit ae235b
    {
Packit ae235b
    case FILE_ACTION_ADDED:
Packit ae235b
      fme = G_FILE_MONITOR_EVENT_CREATED;
Packit ae235b
      break;
Packit ae235b
Packit ae235b
    case FILE_ACTION_REMOVED:
Packit ae235b
      fme = G_FILE_MONITOR_EVENT_DELETED;
Packit ae235b
      break;
Packit ae235b
Packit ae235b
    case FILE_ACTION_MODIFIED:
Packit ae235b
      {
Packit ae235b
        gboolean success_attribs = GetFileAttributesExW (monitor->wfullpath_with_long_prefix,
Packit ae235b
                                                         GetFileExInfoStandard,
Packit ae235b
                                                         &attrib_data);
Packit ae235b
Packit ae235b
        if (monitor->file_attribs != INVALID_FILE_ATTRIBUTES &&
Packit ae235b
            success_attribs &&
Packit ae235b
            attrib_data.dwFileAttributes != monitor->file_attribs)
Packit ae235b
          fme = G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED;
Packit ae235b
        else
Packit ae235b
          fme = G_FILE_MONITOR_EVENT_CHANGED;
Packit ae235b
Packit ae235b
        monitor->file_attribs = attrib_data.dwFileAttributes;
Packit ae235b
      }
Packit ae235b
      break;
Packit ae235b
Packit ae235b
    case FILE_ACTION_RENAMED_OLD_NAME:
Packit ae235b
      if (pfni->NextEntryOffset != 0)
Packit ae235b
        {
Packit ae235b
          /* If the file was renamed in the same directory, we would get a
Packit ae235b
           * FILE_ACTION_RENAMED_NEW_NAME action in the next FILE_NOTIFY_INFORMATION
Packit ae235b
           * structure.
Packit ae235b
           */
Packit ae235b
          glong file_name_len = 0;
Packit ae235b
Packit ae235b
          pfni_next = (PFILE_NOTIFY_INFORMATION) ((BYTE*)pfni + pfni->NextEntryOffset);
Packit ae235b
          renamed_file = g_utf16_to_utf8 (pfni_next->FileName, pfni_next->FileNameLength / sizeof(WCHAR), NULL, &file_name_len, NULL);
Packit ae235b
          if (pfni_next->Action == FILE_ACTION_RENAMED_NEW_NAME)
Packit ae235b
           fme = G_FILE_MONITOR_EVENT_RENAMED;
Packit ae235b
          else
Packit ae235b
           fme = G_FILE_MONITOR_EVENT_MOVED_OUT;
Packit ae235b
        }
Packit ae235b
      else
Packit ae235b
        fme = G_FILE_MONITOR_EVENT_MOVED_OUT;
Packit ae235b
      break;
Packit ae235b
Packit ae235b
    case FILE_ACTION_RENAMED_NEW_NAME:
Packit ae235b
      if (monitor->pfni_prev != NULL &&
Packit ae235b
          monitor->pfni_prev->Action == FILE_ACTION_RENAMED_OLD_NAME)
Packit ae235b
        {
Packit ae235b
          /* don't bother sending events, was already sent (rename) */
Packit ae235b
          fme = -1;
Packit ae235b
        }
Packit ae235b
      else
Packit ae235b
        fme = G_FILE_MONITOR_EVENT_MOVED_IN;
Packit ae235b
      break;
Packit ae235b
Packit ae235b
    default:
Packit ae235b
      /* The possible Windows actions are all above, so shouldn't get here */
Packit ae235b
      g_assert_not_reached ();
Packit ae235b
      break;
Packit ae235b
    }
Packit ae235b
Packit ae235b
  if (fme != -1)
Packit ae235b
    return g_file_monitor_source_handle_event (monitor->fms,
Packit ae235b
                                               fme,
Packit ae235b
                                               filename,
Packit ae235b
                                               renamed_file,
Packit ae235b
                                               NULL,
Packit ae235b
                                               g_get_monotonic_time ());
Packit ae235b
  else
Packit ae235b
    return FALSE;
Packit ae235b
}
Packit ae235b
Packit ae235b
Packit ae235b
static void CALLBACK
Packit ae235b
g_win32_fs_monitor_callback (DWORD        error,
Packit ae235b
                             DWORD        nBytes,
Packit ae235b
                             LPOVERLAPPED lpOverlapped)
Packit ae235b
{
Packit ae235b
  gulong offset;
Packit ae235b
  PFILE_NOTIFY_INFORMATION pfile_notify_walker;
Packit ae235b
  GWin32FSMonitorPrivate *monitor = (GWin32FSMonitorPrivate *) lpOverlapped;
Packit ae235b
Packit ae235b
  DWORD notify_filter = monitor->isfile ?
Packit ae235b
                        (FILE_NOTIFY_CHANGE_FILE_NAME |
Packit ae235b
                         FILE_NOTIFY_CHANGE_ATTRIBUTES |
Packit ae235b
                         FILE_NOTIFY_CHANGE_SIZE) :
Packit ae235b
                        (FILE_NOTIFY_CHANGE_FILE_NAME |
Packit ae235b
                         FILE_NOTIFY_CHANGE_DIR_NAME |
Packit ae235b
                         FILE_NOTIFY_CHANGE_ATTRIBUTES |
Packit ae235b
                         FILE_NOTIFY_CHANGE_SIZE);
Packit ae235b
Packit ae235b
  /* If monitor->self is NULL the GWin32FileMonitor object has been destroyed. */
Packit ae235b
  if (monitor->self == NULL ||
Packit ae235b
      g_file_monitor_is_cancelled (monitor->self) ||
Packit ae235b
      monitor->file_notify_buffer == NULL)
Packit ae235b
    {
Packit ae235b
      g_free (monitor->file_notify_buffer);
Packit ae235b
      g_free (monitor);
Packit ae235b
      return;
Packit ae235b
    }
Packit ae235b
Packit ae235b
  offset = 0;
Packit ae235b
Packit ae235b
  do
Packit ae235b
    {
Packit ae235b
      pfile_notify_walker = (PFILE_NOTIFY_INFORMATION)((BYTE *)monitor->file_notify_buffer + offset);
Packit ae235b
      if (pfile_notify_walker->Action > 0)
Packit ae235b
        {
Packit ae235b
          glong file_name_len;
Packit ae235b
          gchar *changed_file;
Packit ae235b
Packit ae235b
          changed_file = g_utf16_to_utf8 (pfile_notify_walker->FileName,
Packit ae235b
                                          pfile_notify_walker->FileNameLength / sizeof(WCHAR),
Packit ae235b
                                          NULL, &file_name_len, NULL);
Packit ae235b
Packit ae235b
          if (monitor->isfile)
Packit ae235b
            {
Packit ae235b
              gint long_filename_length = wcslen (monitor->wfilename_long);
Packit ae235b
              gint short_filename_length = wcslen (monitor->wfilename_short);
Packit ae235b
              enum GWin32FileMonitorFileAlias alias_state;
Packit ae235b
Packit ae235b
              /* If monitoring a file, check that the changed file
Packit ae235b
              * in the directory matches the file that is to be monitored
Packit ae235b
              * We need to check both the long and short file names for the same file.
Packit ae235b
              *
Packit ae235b
              * We need to send in the name of the monitored file, not its long (or short) variant,
Packit ae235b
              * if they exist.
Packit ae235b
              */
Packit ae235b
Packit ae235b
              if (_wcsnicmp (pfile_notify_walker->FileName,
Packit ae235b
                             monitor->wfilename_long,
Packit ae235b
                             long_filename_length) == 0)
Packit ae235b
                {
Packit ae235b
                  if (_wcsnicmp (pfile_notify_walker->FileName,
Packit ae235b
                                 monitor->wfilename_short,
Packit ae235b
                                 short_filename_length) == 0)
Packit ae235b
                    {
Packit ae235b
                      alias_state = G_WIN32_FILE_MONITOR_NO_ALIAS;
Packit ae235b
                    }
Packit ae235b
                  else
Packit ae235b
                    alias_state = G_WIN32_FILE_MONITOR_LONG_FILENAME;
Packit ae235b
                }
Packit ae235b
              else if (_wcsnicmp (pfile_notify_walker->FileName,
Packit ae235b
                                  monitor->wfilename_short,
Packit ae235b
                                  short_filename_length) == 0)
Packit ae235b
                {
Packit ae235b
                  alias_state = G_WIN32_FILE_MONITOR_SHORT_FILENAME;
Packit ae235b
                }
Packit ae235b
              else
Packit ae235b
                alias_state = G_WIN32_FILE_MONITOR_NO_MATCH_FOUND;
Packit ae235b
Packit ae235b
              if (alias_state != G_WIN32_FILE_MONITOR_NO_MATCH_FOUND)
Packit ae235b
                {
Packit ae235b
                  wchar_t *monitored_file_w;
Packit ae235b
                  gchar *monitored_file;
Packit ae235b
Packit ae235b
                  switch (alias_state)
Packit ae235b
                    {
Packit ae235b
                    case G_WIN32_FILE_MONITOR_NO_ALIAS:
Packit ae235b
                      monitored_file = g_strdup (changed_file);
Packit ae235b
                      break;
Packit ae235b
                    case G_WIN32_FILE_MONITOR_LONG_FILENAME:
Packit ae235b
                    case G_WIN32_FILE_MONITOR_SHORT_FILENAME:
Packit ae235b
                      monitored_file_w = wcsrchr (monitor->wfullpath_with_long_prefix, L'\\');
Packit ae235b
                      monitored_file = g_utf16_to_utf8 (monitored_file_w + 1, -1, NULL, NULL, NULL);
Packit ae235b
                      break;
Packit ae235b
                    default:
Packit ae235b
                      g_assert_not_reached ();
Packit ae235b
                      break;
Packit ae235b
                    }
Packit ae235b
Packit ae235b
                  g_win32_fs_monitor_handle_event (monitor, monitored_file, pfile_notify_walker);
Packit ae235b
                  g_free (monitored_file);
Packit ae235b
                }
Packit ae235b
            }
Packit ae235b
          else
Packit ae235b
            g_win32_fs_monitor_handle_event (monitor, changed_file, pfile_notify_walker);
Packit ae235b
Packit ae235b
          g_free (changed_file);
Packit ae235b
        }
Packit ae235b
Packit ae235b
      monitor->pfni_prev = pfile_notify_walker;
Packit ae235b
      offset += pfile_notify_walker->NextEntryOffset;
Packit ae235b
    }
Packit ae235b
  while (pfile_notify_walker->NextEntryOffset);
Packit ae235b
Packit ae235b
  ReadDirectoryChangesW (monitor->hDirectory,
Packit ae235b
                         monitor->file_notify_buffer,
Packit ae235b
                         monitor->buffer_allocated_bytes,
Packit ae235b
                         FALSE,
Packit ae235b
                         notify_filter,
Packit ae235b
                         &monitor->buffer_filled_bytes,
Packit ae235b
                         &monitor->overlapped,
Packit ae235b
                         g_win32_fs_monitor_callback);
Packit ae235b
}
Packit ae235b
Packit ae235b
void
Packit ae235b
g_win32_fs_monitor_init (GWin32FSMonitorPrivate *monitor,
Packit ae235b
                         const gchar *dirname,
Packit ae235b
                         const gchar *filename,
Packit ae235b
                         gboolean isfile)
Packit ae235b
{
Packit ae235b
  wchar_t *wdirname_with_long_prefix = NULL;
Packit ae235b
  const gchar LONGPFX[] = "\\\\?\\";
Packit ae235b
  gchar *fullpath_with_long_prefix, *dirname_with_long_prefix;
Packit ae235b
  DWORD notify_filter = isfile ?
Packit ae235b
                        (FILE_NOTIFY_CHANGE_FILE_NAME |
Packit ae235b
                         FILE_NOTIFY_CHANGE_ATTRIBUTES |
Packit ae235b
                         FILE_NOTIFY_CHANGE_SIZE) :
Packit ae235b
                        (FILE_NOTIFY_CHANGE_FILE_NAME |
Packit ae235b
                         FILE_NOTIFY_CHANGE_DIR_NAME |
Packit ae235b
                         FILE_NOTIFY_CHANGE_ATTRIBUTES |
Packit ae235b
                         FILE_NOTIFY_CHANGE_SIZE);
Packit ae235b
Packit ae235b
  gboolean success_attribs;
Packit ae235b
  WIN32_FILE_ATTRIBUTE_DATA attrib_data = {0, };
Packit ae235b
Packit ae235b
Packit ae235b
  if (dirname != NULL)
Packit ae235b
    {
Packit ae235b
      dirname_with_long_prefix = g_strconcat (LONGPFX, dirname, NULL);
Packit ae235b
      wdirname_with_long_prefix = g_utf8_to_utf16 (dirname_with_long_prefix, -1, NULL, NULL, NULL);
Packit ae235b
Packit ae235b
      if (isfile)
Packit ae235b
        {
Packit ae235b
          gchar *fullpath;
Packit ae235b
          wchar_t wlongname[MAX_PATH_LONG];
Packit ae235b
          wchar_t wshortname[MAX_PATH_LONG];
Packit ae235b
          wchar_t *wfullpath, *wbasename_long, *wbasename_short;
Packit ae235b
Packit ae235b
          fullpath = g_build_filename (dirname, filename, NULL);
Packit ae235b
          fullpath_with_long_prefix = g_strconcat (LONGPFX, fullpath, NULL);
Packit ae235b
Packit ae235b
          wfullpath = g_utf8_to_utf16 (fullpath, -1, NULL, NULL, NULL);
Packit ae235b
Packit ae235b
          monitor->wfullpath_with_long_prefix =
Packit ae235b
            g_utf8_to_utf16 (fullpath_with_long_prefix, -1, NULL, NULL, NULL);
Packit ae235b
Packit ae235b
          /* ReadDirectoryChangesW() can return the normal filename or the
Packit ae235b
           * "8.3" format filename, so we need to keep track of both these names
Packit ae235b
           * so that we can check against them later when it returns
Packit ae235b
           */
Packit ae235b
          if (GetLongPathNameW (monitor->wfullpath_with_long_prefix, wlongname, MAX_PATH_LONG) == 0)
Packit ae235b
            {
Packit ae235b
              wbasename_long = wcsrchr (monitor->wfullpath_with_long_prefix, L'\\');
Packit ae235b
              monitor->wfilename_long = wbasename_long != NULL ?
Packit ae235b
                                        wcsdup (wbasename_long + 1) :
Packit ae235b
                                        wcsdup (wfullpath);
Packit ae235b
            }
Packit ae235b
          else
Packit ae235b
            {
Packit ae235b
              wbasename_long = wcsrchr (wlongname, L'\\');
Packit ae235b
              monitor->wfilename_long = wbasename_long != NULL ?
Packit ae235b
                                        wcsdup (wbasename_long + 1) :
Packit ae235b
                                        wcsdup (wlongname);
Packit ae235b
Packit ae235b
            }
Packit ae235b
Packit ae235b
          if (GetShortPathNameW (monitor->wfullpath_with_long_prefix, wshortname, MAX_PATH_LONG) == 0)
Packit ae235b
            {
Packit ae235b
              wbasename_short = wcsrchr (monitor->wfullpath_with_long_prefix, L'\\');
Packit ae235b
              monitor->wfilename_short = wbasename_short != NULL ?
Packit ae235b
                                         wcsdup (wbasename_short + 1) :
Packit ae235b
                                         wcsdup (wfullpath);
Packit ae235b
            }
Packit ae235b
          else
Packit ae235b
            {
Packit ae235b
              wbasename_short = wcsrchr (wshortname, L'\\');
Packit ae235b
              monitor->wfilename_short = wbasename_short != NULL ?
Packit ae235b
                                         wcsdup (wbasename_short + 1) :
Packit ae235b
                                         wcsdup (wshortname);
Packit ae235b
            }
Packit ae235b
Packit ae235b
          g_free (fullpath);
Packit ae235b
        }
Packit ae235b
      else
Packit ae235b
        {
Packit ae235b
          monitor->wfilename_short = NULL;
Packit ae235b
          monitor->wfilename_long = NULL;
Packit ae235b
          monitor->wfullpath_with_long_prefix = g_utf8_to_utf16 (dirname_with_long_prefix, -1, NULL, NULL, NULL);
Packit ae235b
        }
Packit ae235b
Packit ae235b
      monitor->isfile = isfile;
Packit ae235b
    }
Packit ae235b
  else
Packit ae235b
    {
Packit ae235b
      dirname_with_long_prefix = g_strconcat (LONGPFX, filename, NULL);
Packit ae235b
      monitor->wfullpath_with_long_prefix = g_utf8_to_utf16 (dirname_with_long_prefix, -1, NULL, NULL, NULL);
Packit ae235b
      monitor->wfilename_long = NULL;
Packit ae235b
      monitor->wfilename_short = NULL;
Packit ae235b
      monitor->isfile = FALSE;
Packit ae235b
    }
Packit ae235b
Packit ae235b
  success_attribs = GetFileAttributesExW (monitor->wfullpath_with_long_prefix,
Packit ae235b
                                          GetFileExInfoStandard,
Packit ae235b
                                          &attrib_data);
Packit ae235b
  if (success_attribs)
Packit ae235b
    monitor->file_attribs = attrib_data.dwFileAttributes; /* Store up original attributes */
Packit ae235b
  else
Packit ae235b
    monitor->file_attribs = INVALID_FILE_ATTRIBUTES;
Packit ae235b
  monitor->pfni_prev = NULL;
Packit ae235b
  monitor->hDirectory = CreateFileW (wdirname_with_long_prefix != NULL ? wdirname_with_long_prefix : monitor->wfullpath_with_long_prefix,
Packit ae235b
                                     FILE_GENERIC_READ | FILE_GENERIC_WRITE,
Packit ae235b
                                     FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
Packit ae235b
                                     NULL,
Packit ae235b
                                     OPEN_EXISTING,
Packit ae235b
                                     FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
Packit ae235b
                                     NULL);
Packit ae235b
Packit ae235b
  g_free (wdirname_with_long_prefix);
Packit ae235b
  g_free (dirname_with_long_prefix);
Packit ae235b
Packit ae235b
  if (monitor->hDirectory != INVALID_HANDLE_VALUE)
Packit ae235b
    {
Packit ae235b
      ReadDirectoryChangesW (monitor->hDirectory,
Packit ae235b
                             monitor->file_notify_buffer,
Packit ae235b
                             monitor->buffer_allocated_bytes,
Packit ae235b
                             FALSE,
Packit ae235b
                             notify_filter,
Packit ae235b
                             &monitor->buffer_filled_bytes,
Packit ae235b
                             &monitor->overlapped,
Packit ae235b
                             g_win32_fs_monitor_callback);
Packit ae235b
    }
Packit ae235b
}
Packit ae235b
Packit ae235b
GWin32FSMonitorPrivate *
Packit ae235b
g_win32_fs_monitor_create (gboolean isfile)
Packit ae235b
{
Packit ae235b
  GWin32FSMonitorPrivate *monitor = g_new0 (GWin32FSMonitorPrivate, 1);
Packit ae235b
Packit ae235b
  monitor->buffer_allocated_bytes = 32784;
Packit ae235b
  monitor->file_notify_buffer = g_new0 (FILE_NOTIFY_INFORMATION, monitor->buffer_allocated_bytes);
Packit ae235b
Packit ae235b
  return monitor;
Packit ae235b
}
Packit ae235b
Packit ae235b
void
Packit ae235b
g_win32_fs_monitor_finalize (GWin32FSMonitorPrivate *monitor)
Packit ae235b
{
Packit ae235b
  g_free (monitor->wfullpath_with_long_prefix);
Packit ae235b
  g_free (monitor->wfilename_long);
Packit ae235b
  g_free (monitor->wfilename_short);
Packit ae235b
Packit ae235b
  if (monitor->hDirectory == INVALID_HANDLE_VALUE)
Packit ae235b
    {
Packit ae235b
      /* If we don't have a directory handle we can free
Packit ae235b
       * monitor->file_notify_buffer and monitor here. The
Packit ae235b
       * callback won't be called obviously any more (and presumably
Packit ae235b
       * never has been called).
Packit ae235b
       */
Packit ae235b
      g_free (monitor->file_notify_buffer);
Packit ae235b
      monitor->file_notify_buffer = NULL;
Packit ae235b
      g_free (monitor);
Packit ae235b
    }
Packit ae235b
  else
Packit ae235b
    {
Packit ae235b
      /* If we have a directory handle, the OVERLAPPED struct is
Packit ae235b
       * passed once more to the callback as a result of the
Packit ae235b
       * CloseHandle() done in the cancel method, so monitor has to
Packit ae235b
       * be kept around. The GWin32DirectoryMonitor object is
Packit ae235b
       * disappearing, so can't leave a pointer to it in
Packit ae235b
       * monitor->self.
Packit ae235b
       */
Packit ae235b
      monitor->self = NULL;
Packit ae235b
    }
Packit ae235b
}
Packit ae235b
Packit ae235b
void
Packit ae235b
g_win32_fs_monitor_close_handle (GWin32FSMonitorPrivate *monitor)
Packit ae235b
{
Packit ae235b
  /* This triggers a last callback() with nBytes==0. */
Packit ae235b
Packit ae235b
  /* Actually I am not so sure about that, it seems to trigger a last
Packit ae235b
   * callback allright, but the way to recognize that it is the final
Packit ae235b
   * one is not to check for nBytes==0, I think that was a
Packit ae235b
   * misunderstanding.
Packit ae235b
   */
Packit ae235b
  if (monitor->hDirectory != INVALID_HANDLE_VALUE)
Packit ae235b
    CloseHandle (monitor->hDirectory);
Packit ae235b
}