blob: e0a51e2f8bd80ac1a1a09ced084516ecbfe2ff2a [file] [log] [blame]
/* 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);
}