mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-09-11 02:18:43 +02:00
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);
|
||
|
}
|