mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-09-10 09:58:44 +02:00
This updates the file monitoring on Windows by: -Clean up the code, and ensure things are indeed being freed when we exit. -Have attributes changes in files (when monitoring directories) properly done. -Split up the code, to ease readability To Possibly Do: -Check on whether we want to monitor the case when we try to pull out a USB stick without ejecting in in Windows (the monitoring mechanism understandbly denies such ejection requests, since the monitored file/directory is obviously in use in this case) -Investigate on the interesting/boredom algorithm, whether we can do it here. Possible Limitations: -If moving a file out of the directory (or vice versa), the system reports that as a delete event (or create event in the vice versa case), so in the current form without using NTFS hournals (which is not optimal as that would trigger UAC), so we can't reliably emit MOVE OUT or MOVE IN events on Windows. -Hard links are supported transparently, but notifications are only sent by the system on changes when the monitored file/directory (or monitored hard link) is being changed, when the file is modified via the hard link or original file respectively in this case. https://bugzilla.gnome.org/show_bug.cgi?id=730116
455 lines
17 KiB
C
455 lines
17 KiB
C
/* GIO - GLib Input, Output and Streaming Library
|
|
*
|
|
* Copyright (C) 2006-2015 Red Hat, Inc.
|
|
* Copyright (C) 2015 Chun-wei Fan
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General
|
|
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Author: Vlad Grecescu <b100dian@gmail.com>
|
|
* Author: Chun-wei Fan <fanc999@yahoo.com.tw>
|
|
*
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "gwin32filemonitorutils.h"
|
|
#include "gwin32filemonitorevents.h"
|
|
#include "gio/gfile.h"
|
|
|
|
/*
|
|
* We are using the Wide/Unicode (W) variants of the Windows APIs as:
|
|
* -There isn't a ReadDirectoryChangesA()
|
|
* -We want to support long paths, over 256 characters, and using the W variants is required in this case
|
|
*/
|
|
|
|
/*
|
|
* Note: We don't support monitoring for "normal" unmounts in Windows as the Windows file/directory monitoring
|
|
* mechanism will disallow the unmounting of the monitored volume (unless of course we pull out the USB stick,
|
|
* for instance, without ejecting it
|
|
*/
|
|
|
|
/*
|
|
* On Windows, there is the notion of hard links, but they are treated more or less like standard files, but any
|
|
* changes to them will also cause a notification to be sent for the directory where the original file resides,
|
|
* so ReadDirectoryChangesW() also works with hard links, though transparently, so there isn't a special code path
|
|
* for hard links here. The notification, however, may be sent only when the file (or hard link) in question
|
|
* is being accessed.
|
|
*/
|
|
|
|
gboolean
|
|
g_win32_file_monitor_long_pfx_needed (const gchar* path)
|
|
{
|
|
if (g_utf8_strlen (path, -1) > MAX_PATH)
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
GHashTable*
|
|
g_win32_file_monitor_dir_refresh_attribs (GWin32FileMonitorPrivate *monitor,
|
|
GWin32FileMonitorInfo *info)
|
|
{
|
|
GHashTable *ht_attribs;
|
|
WIN32_FILE_ATTRIBUTE_DATA *attrib_data;
|
|
gchar *fullpath, *dirname, *dirname_casefold;
|
|
wchar_t *wfullpath;
|
|
HANDLE hFind = INVALID_HANDLE_VALUE;
|
|
WIN32_FIND_DATA file_data = {0,};
|
|
|
|
if (info->isfile)
|
|
return NULL;
|
|
|
|
fullpath = g_strconcat (info->dirname_with_long_prefix,
|
|
G_DIR_SEPARATOR_S,
|
|
info->name,
|
|
G_DIR_SEPARATOR_S,
|
|
"*",
|
|
NULL);
|
|
wfullpath = g_utf8_to_utf16 (fullpath, -1, NULL, NULL, NULL);
|
|
dirname = g_strconcat (info->dirname_with_long_prefix + STRIP_PFX_LENGTH,
|
|
G_DIR_SEPARATOR_S,
|
|
info->name,
|
|
NULL);
|
|
dirname_casefold = g_utf8_casefold (dirname, -1);
|
|
|
|
hFind = FindFirstFileExW (wfullpath,
|
|
FindExInfoBasic,
|
|
&file_data,
|
|
0,
|
|
NULL,
|
|
0);
|
|
|
|
ht_attribs = g_hash_table_lookup (monitor->ht_files_attribs, dirname_casefold);
|
|
if (ht_attribs == NULL)
|
|
{
|
|
ht_attribs = g_hash_table_new_full (g_str_hash,
|
|
g_str_equal,
|
|
g_free,
|
|
g_free);
|
|
}
|
|
else
|
|
g_hash_table_remove_all (ht_attribs);
|
|
|
|
g_free (dirname_casefold);
|
|
g_free (dirname);
|
|
|
|
if (hFind == INVALID_HANDLE_VALUE)
|
|
/*
|
|
* Initially, we won't get here, if we started monitoring on a non-existing directory.
|
|
* If we do later on, the directory was moved or deleted, forget about updating the info
|
|
*/
|
|
return NULL;
|
|
else
|
|
{
|
|
do
|
|
{
|
|
attrib_data = g_new0 (WIN32_FILE_ATTRIBUTE_DATA, 1);
|
|
attrib_data->dwFileAttributes = file_data.dwFileAttributes;
|
|
attrib_data->ftCreationTime = file_data.ftCreationTime;
|
|
attrib_data->ftLastAccessTime = file_data.ftLastAccessTime;
|
|
attrib_data->ftLastWriteTime = file_data.ftLastWriteTime;
|
|
attrib_data->nFileSizeHigh = file_data.nFileSizeHigh;
|
|
attrib_data->nFileSizeLow = file_data.nFileSizeLow;
|
|
|
|
g_hash_table_insert (ht_attribs,
|
|
g_utf8_casefold (file_data.cFileName, -1),
|
|
attrib_data);
|
|
}
|
|
while (FindNextFile (hFind, &file_data));
|
|
FindClose (hFind);
|
|
}
|
|
return ht_attribs;
|
|
}
|
|
|
|
/*
|
|
* ReadDirectoryChangesW() can return the normal filename or the
|
|
* "8.3" format filename, so we need to keep track of both these names
|
|
* so that we can check against them later when it returns
|
|
*/
|
|
void
|
|
g_win32_file_monitor_set_names (GWin32FileMonitorInfo *info)
|
|
{
|
|
wchar_t *wfullpath, *wbasename_long, *wbasename_short;
|
|
wchar_t wlongname[MAX_PATH_LONG];
|
|
wchar_t wshortname[MAX_PATH_LONG];
|
|
gboolean success_attribs;
|
|
|
|
gchar *fullpath_with_long_pfx = g_strconcat (info->dirname_with_long_prefix,
|
|
G_DIR_SEPARATOR_S,
|
|
info->name,
|
|
NULL);
|
|
|
|
wfullpath = g_utf8_to_utf16 (fullpath_with_long_pfx, -1, NULL, NULL, NULL);
|
|
if (info->longname != NULL)
|
|
g_free (info->longname);
|
|
if (info->shortname != NULL)
|
|
g_free (info->shortname);
|
|
|
|
/*
|
|
* Get long and short paths of the specified path, if it exists, otherwise
|
|
* just assume the filename part in the passed in filename and directory as
|
|
* the long and short filenames
|
|
*/
|
|
if (GetLongPathNameW (wfullpath, wlongname, MAX_PATH_LONG) != 0)
|
|
{
|
|
wbasename_long = wcsrchr (wlongname, G_DIR_SEPARATOR_W) != NULL ?
|
|
wcsrchr (wlongname, G_DIR_SEPARATOR_W) + 1 :
|
|
wlongname + STRIP_PFX_LENGTH;
|
|
}
|
|
else
|
|
{
|
|
wbasename_long = wcsrchr (wfullpath, G_DIR_SEPARATOR_W) != NULL ?
|
|
wcsrchr (wfullpath, G_DIR_SEPARATOR_W) + 1 :
|
|
wfullpath + STRIP_PFX_LENGTH;
|
|
}
|
|
if (GetShortPathNameW (wfullpath, wshortname, MAX_PATH_LONG) != 0)
|
|
{
|
|
wbasename_short = wcsrchr (wshortname, G_DIR_SEPARATOR_W) != NULL ?
|
|
wcsrchr (wshortname, G_DIR_SEPARATOR_W) + 1 :
|
|
wshortname + STRIP_PFX_LENGTH;
|
|
}
|
|
else
|
|
{
|
|
wbasename_short = wcsrchr (wfullpath, G_DIR_SEPARATOR_W) != NULL ?
|
|
wcsrchr (wfullpath, G_DIR_SEPARATOR_W) + 1 :
|
|
wfullpath + STRIP_PFX_LENGTH;
|
|
}
|
|
|
|
info->longname = g_utf16_to_utf8 (wbasename_long, -1, NULL, NULL, NULL);
|
|
info->shortname = g_utf16_to_utf8 (wbasename_short, -1, NULL, NULL, NULL);
|
|
|
|
/* Store up current file attributes */
|
|
success_attribs = GetFileAttributesExW (wfullpath,
|
|
GetFileExInfoStandard,
|
|
&info->attribs);
|
|
|
|
g_free (wfullpath);
|
|
g_free (fullpath_with_long_pfx);
|
|
|
|
if (!success_attribs)
|
|
info->attribs.dwFileAttributes = INVALID_FILE_ATTRIBUTES;
|
|
}
|
|
|
|
gboolean
|
|
g_win32_file_monitor_check_attrib_changed (GWin32FileMonitorInfo *info,
|
|
gchar *filename,
|
|
GHashTable *ht_attribs)
|
|
{
|
|
gchar *fullpath_with_long_prefix = g_strconcat (info->dirname_with_long_prefix,
|
|
G_DIR_SEPARATOR_S,
|
|
filename,
|
|
NULL);
|
|
wchar_t *wfullpath_with_long_prefix = g_utf8_to_utf16 (fullpath_with_long_prefix, -1, NULL, NULL, NULL);
|
|
gboolean is_attribs_changed = FALSE;
|
|
WIN32_FILE_ATTRIBUTE_DATA attrib_data = {0, };
|
|
gboolean success_attribs = GetFileAttributesExW (wfullpath_with_long_prefix,
|
|
GetFileExInfoStandard,
|
|
&attrib_data);
|
|
|
|
if (!success_attribs)
|
|
attrib_data.dwFileAttributes = INVALID_FILE_ATTRIBUTES;
|
|
|
|
if (info->isfile)
|
|
{
|
|
/*
|
|
* Monitoring a file: Simply check if attributes changed, or file changed, and
|
|
* store up the new attributes for the file if needed.
|
|
*/
|
|
if (info->attribs.dwFileAttributes != INVALID_FILE_ATTRIBUTES &&
|
|
success_attribs &&
|
|
attrib_data.dwFileAttributes != info->attribs.dwFileAttributes)
|
|
{
|
|
is_attribs_changed = TRUE;
|
|
info->attribs = attrib_data;
|
|
}
|
|
return is_attribs_changed;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Monitoring directories: The situation is more complex, we need to first see whether
|
|
* a file changed, or a directory changed. If it is just the directory that changed,
|
|
* than it's like the file case, that is to check only the attribute of the directory.
|
|
* If it is on a file in a directory, we need to check against the attributes of the file,
|
|
* against the attribues of the files in the directory, which we stored up when we were
|
|
* creating the monitor object, and update our stored records on that file if the attributes
|
|
* of that file changed.
|
|
*/
|
|
|
|
WIN32_FILE_ATTRIBUTE_DATA *attrib_data_orig;
|
|
gchar *filename_casefold;
|
|
|
|
if (g_utf8_strrchr (filename, -1, G_DIR_SEPARATOR) != NULL)
|
|
{
|
|
/* A file in the monitored directory changed */
|
|
gboolean attribs_success;
|
|
gchar *longname;
|
|
wchar_t wfullpath_long[MAX_PATH_LONG];
|
|
|
|
WIN32_FILE_ATTRIBUTE_DATA *curr_attrib_data = g_new0 (WIN32_FILE_ATTRIBUTE_DATA, 1);
|
|
gchar *fullpath = g_strconcat (info->dirname_with_long_prefix,
|
|
G_DIR_SEPARATOR_S,
|
|
info->name,
|
|
G_DIR_SEPARATOR_S,
|
|
g_utf8_strrchr (filename, -1, G_DIR_SEPARATOR) + 1,
|
|
NULL);
|
|
wchar_t *wfullpath = g_utf8_to_utf16 (fullpath, -1, NULL, NULL, NULL);
|
|
|
|
attribs_success = GetFileAttributesExW (wfullpath,
|
|
GetFileExInfoStandard,
|
|
curr_attrib_data);
|
|
|
|
if (!attribs_success)
|
|
{
|
|
curr_attrib_data->dwFileAttributes = INVALID_FILE_ATTRIBUTES;
|
|
longname = g_strdup (g_utf8_strrchr (longname, -1, G_DIR_SEPARATOR) + 1);
|
|
}
|
|
else
|
|
{
|
|
if (GetLongPathNameW (wfullpath, wfullpath_long, MAX_PATH_LONG) != 0)
|
|
longname = g_utf16_to_utf8 (wcsrchr (wfullpath_long, G_DIR_SEPARATOR_W) + 1,
|
|
-1,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
else
|
|
longname = g_strdup (g_utf8_strrchr (longname, -1, G_DIR_SEPARATOR) + 1);
|
|
}
|
|
|
|
filename_casefold = g_utf8_casefold (longname, -1);
|
|
g_free (longname);
|
|
attrib_data_orig = g_hash_table_lookup (ht_attribs, filename_casefold);
|
|
|
|
if (attrib_data_orig->dwFileAttributes != INVALID_FILE_ATTRIBUTES &&
|
|
success_attribs &&
|
|
curr_attrib_data->dwFileAttributes != attrib_data_orig->dwFileAttributes)
|
|
{
|
|
is_attribs_changed = TRUE;
|
|
}
|
|
|
|
if (is_attribs_changed)
|
|
g_hash_table_replace (ht_attribs, g_strdup (filename_casefold), curr_attrib_data);
|
|
else
|
|
g_free (curr_attrib_data);
|
|
|
|
g_free (filename_casefold);
|
|
return is_attribs_changed;
|
|
}
|
|
else
|
|
{
|
|
/* The monitored directory itself changed */
|
|
if (info->attribs.dwFileAttributes != INVALID_FILE_ATTRIBUTES &&
|
|
success_attribs &&
|
|
attrib_data.dwFileAttributes != info->attribs.dwFileAttributes)
|
|
{
|
|
is_attribs_changed = TRUE;
|
|
info->attribs = attrib_data;
|
|
}
|
|
return is_attribs_changed;
|
|
}
|
|
}
|
|
}
|
|
|
|
static GWin32FileMonitorInfo *
|
|
g_win32_file_monitor_set_paths (GWin32FileMonitorPrivate *monitor,
|
|
const gchar *dirname,
|
|
const gchar *filename)
|
|
{
|
|
GWin32FileMonitorInfo *info = g_new0 (GWin32FileMonitorInfo, 1);
|
|
gboolean updated;
|
|
|
|
if (filename != NULL)
|
|
{
|
|
/* monitor a file */
|
|
gchar *fullpath;
|
|
info->isfile = TRUE;
|
|
info->dirname_with_long_prefix = g_strconcat (LONGPFX, dirname, NULL);
|
|
info->name = g_strdup (filename);
|
|
fullpath = g_strconcat (dirname,
|
|
G_DIR_SEPARATOR_S,
|
|
info->name,
|
|
NULL);
|
|
updated = g_hash_table_replace (monitor->ht_watched_names,
|
|
g_utf8_casefold (fullpath, -1),
|
|
info);
|
|
g_free (fullpath);
|
|
}
|
|
else
|
|
{
|
|
/* monitor a directory */
|
|
gchar *parentdir = g_malloc0 (g_utf8_strlen (dirname, -1) + 1);
|
|
GHashTable *ht_attribs;
|
|
|
|
g_utf8_strncpy (parentdir,
|
|
dirname,
|
|
g_utf8_strlen (dirname, -1) -
|
|
g_utf8_strlen (g_utf8_strrchr (dirname, -1, G_DIR_SEPARATOR), -1));
|
|
|
|
info->isfile = FALSE;
|
|
info->dirname_with_long_prefix = g_strconcat (LONGPFX, parentdir, NULL);
|
|
info->name = g_strdup (g_utf8_strrchr (dirname, -1, G_DIR_SEPARATOR) + 1);
|
|
|
|
g_free (parentdir);
|
|
|
|
ht_attribs = g_win32_file_monitor_dir_refresh_attribs (monitor, info);
|
|
|
|
if (ht_attribs != NULL)
|
|
g_hash_table_replace (monitor->ht_files_attribs,
|
|
g_utf8_casefold (dirname, -1),
|
|
ht_attribs);
|
|
g_hash_table_replace (monitor->ht_watched_names,
|
|
g_utf8_casefold (dirname, -1),
|
|
info);
|
|
}
|
|
|
|
g_win32_file_monitor_set_names (info);
|
|
return info;
|
|
}
|
|
|
|
/* Set up call to ReadDirectoryChangesW() */
|
|
static void
|
|
g_win32_file_monitor_begin_monitor (GWin32FileMonitorPrivate *monitor,
|
|
GWin32FileMonitorInfo *info)
|
|
{
|
|
gchar *fullpath;
|
|
wchar_t *wdirname;
|
|
|
|
DWORD notify_filter = info->isfile ?
|
|
(FILE_NOTIFY_CHANGE_FILE_NAME |
|
|
FILE_NOTIFY_CHANGE_ATTRIBUTES |
|
|
FILE_NOTIFY_CHANGE_SIZE) :
|
|
(FILE_NOTIFY_CHANGE_FILE_NAME |
|
|
FILE_NOTIFY_CHANGE_DIR_NAME |
|
|
FILE_NOTIFY_CHANGE_ATTRIBUTES |
|
|
FILE_NOTIFY_CHANGE_SIZE);
|
|
|
|
fullpath = g_strconcat (info->dirname_with_long_prefix + STRIP_PFX_LENGTH,
|
|
G_DIR_SEPARATOR_S,
|
|
info->name,
|
|
NULL);
|
|
|
|
wdirname = g_utf8_to_utf16 (info->dirname_with_long_prefix, -1, NULL, NULL, NULL);
|
|
|
|
monitor->hDirectory = CreateFileW (wdirname,
|
|
FILE_GENERIC_READ | FILE_GENERIC_WRITE,
|
|
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
|
|
NULL);
|
|
|
|
g_hash_table_insert (monitor->ht_watched_dirs,
|
|
monitor->hDirectory,
|
|
g_utf8_casefold (fullpath, -1));
|
|
|
|
if (monitor->hDirectory != INVALID_HANDLE_VALUE)
|
|
{
|
|
ReadDirectoryChangesW (monitor->hDirectory,
|
|
monitor->file_notify_buffer,
|
|
monitor->buffer_allocated_bytes,
|
|
!info->isfile,
|
|
notify_filter,
|
|
&monitor->buffer_filled_bytes,
|
|
&monitor->overlapped,
|
|
g_win32_file_monitor_callback);
|
|
}
|
|
}
|
|
|
|
void
|
|
g_win32_file_monitor_prepare (GWin32FileMonitorPrivate *monitor,
|
|
gchar *dirname,
|
|
gchar *filename,
|
|
gboolean isfile)
|
|
{
|
|
GWin32FileMonitorInfo *info;
|
|
gchar *fullpath_with_long_prefix, *dirname_with_long_prefix;
|
|
DWORD notify_filter = isfile ?
|
|
(FILE_NOTIFY_CHANGE_FILE_NAME |
|
|
FILE_NOTIFY_CHANGE_ATTRIBUTES |
|
|
FILE_NOTIFY_CHANGE_SIZE) :
|
|
(FILE_NOTIFY_CHANGE_FILE_NAME |
|
|
FILE_NOTIFY_CHANGE_DIR_NAME |
|
|
FILE_NOTIFY_CHANGE_ATTRIBUTES |
|
|
FILE_NOTIFY_CHANGE_SIZE);
|
|
|
|
monitor->pfni_prev = NULL;
|
|
|
|
if (isfile)
|
|
info = g_win32_file_monitor_set_paths (monitor, dirname, filename);
|
|
else
|
|
info = g_win32_file_monitor_set_paths (monitor, dirname, NULL);
|
|
|
|
g_win32_file_monitor_begin_monitor (monitor, info);
|
|
}
|