| /* GIO - GLib Input, Output and Streaming Library | |
| * | |
| * Copyright (C) 2006-2007 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.1 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 "gwin32fsmonitorutils.h" | |
| #include "gio/gfile.h" | |
| #include <windows.h> | |
| #define MAX_PATH_LONG 32767 /* Support Paths longer than MAX_PATH (260) characters */ | |
| static gboolean | |
| g_win32_fs_monitor_handle_event (GWin32FSMonitorPrivate *monitor, | |
| const gchar *filename, | |
| PFILE_NOTIFY_INFORMATION pfni) | |
| { | |
| GFileMonitorEvent fme; | |
| PFILE_NOTIFY_INFORMATION pfni_next; | |
| WIN32_FILE_ATTRIBUTE_DATA attrib_data = {0, }; | |
| gchar *renamed_file = NULL; | |
| switch (pfni->Action) | |
| { | |
| case FILE_ACTION_ADDED: | |
| fme = G_FILE_MONITOR_EVENT_CREATED; | |
| break; | |
| case FILE_ACTION_REMOVED: | |
| fme = G_FILE_MONITOR_EVENT_DELETED; | |
| break; | |
| case FILE_ACTION_MODIFIED: | |
| { | |
| gboolean success_attribs = GetFileAttributesExW (monitor->wfullpath_with_long_prefix, | |
| GetFileExInfoStandard, | |
| &attrib_data); | |
| if (monitor->file_attribs != INVALID_FILE_ATTRIBUTES && | |
| success_attribs && | |
| attrib_data.dwFileAttributes != monitor->file_attribs) | |
| fme = G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED; | |
| else | |
| fme = G_FILE_MONITOR_EVENT_CHANGED; | |
| monitor->file_attribs = attrib_data.dwFileAttributes; | |
| } | |
| break; | |
| case FILE_ACTION_RENAMED_OLD_NAME: | |
| if (pfni->NextEntryOffset != 0) | |
| { | |
| /* If the file was renamed in the same directory, we would get a | |
| * FILE_ACTION_RENAMED_NEW_NAME action in the next FILE_NOTIFY_INFORMATION | |
| * structure. | |
| */ | |
| glong file_name_len = 0; | |
| pfni_next = (PFILE_NOTIFY_INFORMATION) ((BYTE*)pfni + pfni->NextEntryOffset); | |
| renamed_file = g_utf16_to_utf8 (pfni_next->FileName, pfni_next->FileNameLength / sizeof(WCHAR), NULL, &file_name_len, NULL); | |
| if (pfni_next->Action == FILE_ACTION_RENAMED_NEW_NAME) | |
| fme = G_FILE_MONITOR_EVENT_RENAMED; | |
| else | |
| fme = G_FILE_MONITOR_EVENT_MOVED_OUT; | |
| } | |
| else | |
| fme = G_FILE_MONITOR_EVENT_MOVED_OUT; | |
| break; | |
| case FILE_ACTION_RENAMED_NEW_NAME: | |
| if (monitor->pfni_prev != NULL && | |
| monitor->pfni_prev->Action == FILE_ACTION_RENAMED_OLD_NAME) | |
| { | |
| /* don't bother sending events, was already sent (rename) */ | |
| fme = (GFileMonitorEvent) -1; | |
| } | |
| else | |
| fme = G_FILE_MONITOR_EVENT_MOVED_IN; | |
| break; | |
| default: | |
| /* The possible Windows actions are all above, so shouldn't get here */ | |
| g_assert_not_reached (); | |
| break; | |
| } | |
| if (fme != (GFileMonitorEvent) -1) | |
| return g_file_monitor_source_handle_event (monitor->fms, | |
| fme, | |
| filename, | |
| renamed_file, | |
| NULL, | |
| g_get_monotonic_time ()); | |
| else | |
| return FALSE; | |
| } | |
| static void CALLBACK | |
| g_win32_fs_monitor_callback (DWORD error, | |
| DWORD nBytes, | |
| LPOVERLAPPED lpOverlapped) | |
| { | |
| gulong offset; | |
| PFILE_NOTIFY_INFORMATION pfile_notify_walker; | |
| GWin32FSMonitorPrivate *monitor = (GWin32FSMonitorPrivate *) lpOverlapped; | |
| DWORD notify_filter = monitor->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); | |
| /* If monitor->self is NULL the GWin32FileMonitor object has been destroyed. */ | |
| if (monitor->self == NULL || | |
| g_file_monitor_is_cancelled (monitor->self) || | |
| monitor->file_notify_buffer == NULL) | |
| { | |
| g_free (monitor->file_notify_buffer); | |
| g_free (monitor); | |
| return; | |
| } | |
| offset = 0; | |
| do | |
| { | |
| pfile_notify_walker = (PFILE_NOTIFY_INFORMATION)((BYTE *)monitor->file_notify_buffer + offset); | |
| if (pfile_notify_walker->Action > 0) | |
| { | |
| glong file_name_len; | |
| gchar *changed_file; | |
| changed_file = g_utf16_to_utf8 (pfile_notify_walker->FileName, | |
| pfile_notify_walker->FileNameLength / sizeof(WCHAR), | |
| NULL, &file_name_len, NULL); | |
| if (monitor->isfile) | |
| { | |
| size_t long_filename_length = wcslen (monitor->wfilename_long); | |
| size_t short_filename_length = wcslen (monitor->wfilename_short); | |
| enum GWin32FileMonitorFileAlias alias_state; | |
| /* If monitoring a file, check that the changed file | |
| * in the directory matches the file that is to be monitored | |
| * We need to check both the long and short file names for the same file. | |
| * | |
| * We need to send in the name of the monitored file, not its long (or short) variant, | |
| * if they exist. | |
| */ | |
| if (_wcsnicmp (pfile_notify_walker->FileName, | |
| monitor->wfilename_long, | |
| long_filename_length) == 0) | |
| { | |
| if (_wcsnicmp (pfile_notify_walker->FileName, | |
| monitor->wfilename_short, | |
| short_filename_length) == 0) | |
| { | |
| alias_state = G_WIN32_FILE_MONITOR_NO_ALIAS; | |
| } | |
| else | |
| alias_state = G_WIN32_FILE_MONITOR_LONG_FILENAME; | |
| } | |
| else if (_wcsnicmp (pfile_notify_walker->FileName, | |
| monitor->wfilename_short, | |
| short_filename_length) == 0) | |
| { | |
| alias_state = G_WIN32_FILE_MONITOR_SHORT_FILENAME; | |
| } | |
| else | |
| alias_state = G_WIN32_FILE_MONITOR_NO_MATCH_FOUND; | |
| if (alias_state != G_WIN32_FILE_MONITOR_NO_MATCH_FOUND) | |
| { | |
| wchar_t *monitored_file_w; | |
| gchar *monitored_file; | |
| switch (alias_state) | |
| { | |
| case G_WIN32_FILE_MONITOR_NO_ALIAS: | |
| monitored_file = g_strdup (changed_file); | |
| break; | |
| case G_WIN32_FILE_MONITOR_LONG_FILENAME: | |
| case G_WIN32_FILE_MONITOR_SHORT_FILENAME: | |
| monitored_file_w = wcsrchr (monitor->wfullpath_with_long_prefix, L'\\'); | |
| monitored_file = g_utf16_to_utf8 (monitored_file_w + 1, -1, NULL, NULL, NULL); | |
| break; | |
| default: | |
| g_assert_not_reached (); | |
| break; | |
| } | |
| g_win32_fs_monitor_handle_event (monitor, monitored_file, pfile_notify_walker); | |
| g_free (monitored_file); | |
| } | |
| } | |
| else | |
| g_win32_fs_monitor_handle_event (monitor, changed_file, pfile_notify_walker); | |
| g_free (changed_file); | |
| } | |
| monitor->pfni_prev = pfile_notify_walker; | |
| offset += pfile_notify_walker->NextEntryOffset; | |
| } | |
| while (pfile_notify_walker->NextEntryOffset); | |
| ReadDirectoryChangesW (monitor->hDirectory, | |
| monitor->file_notify_buffer, | |
| monitor->buffer_allocated_bytes, | |
| FALSE, | |
| notify_filter, | |
| &monitor->buffer_filled_bytes, | |
| &monitor->overlapped, | |
| g_win32_fs_monitor_callback); | |
| } | |
| void | |
| g_win32_fs_monitor_init (GWin32FSMonitorPrivate *monitor, | |
| const gchar *dirname, | |
| const gchar *filename, | |
| gboolean isfile) | |
| { | |
| wchar_t *wdirname_with_long_prefix = NULL; | |
| const gchar LONGPFX[] = "\\\\?\\"; | |
| 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); | |
| gboolean success_attribs; | |
| WIN32_FILE_ATTRIBUTE_DATA attrib_data = {0, }; | |
| if (dirname != NULL) | |
| { | |
| dirname_with_long_prefix = g_strconcat (LONGPFX, dirname, NULL); | |
| wdirname_with_long_prefix = g_utf8_to_utf16 (dirname_with_long_prefix, -1, NULL, NULL, NULL); | |
| if (isfile) | |
| { | |
| gchar *fullpath; | |
| wchar_t wlongname[MAX_PATH_LONG]; | |
| wchar_t wshortname[MAX_PATH_LONG]; | |
| wchar_t *wfullpath, *wbasename_long, *wbasename_short; | |
| fullpath = g_build_filename (dirname, filename, NULL); | |
| fullpath_with_long_prefix = g_strconcat (LONGPFX, fullpath, NULL); | |
| wfullpath = g_utf8_to_utf16 (fullpath, -1, NULL, NULL, NULL); | |
| monitor->wfullpath_with_long_prefix = | |
| g_utf8_to_utf16 (fullpath_with_long_prefix, -1, NULL, NULL, NULL); | |
| /* 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 | |
| */ | |
| if (GetLongPathNameW (monitor->wfullpath_with_long_prefix, wlongname, MAX_PATH_LONG) == 0) | |
| { | |
| wbasename_long = wcsrchr (monitor->wfullpath_with_long_prefix, L'\\'); | |
| monitor->wfilename_long = wbasename_long != NULL ? | |
| wcsdup (wbasename_long + 1) : | |
| wcsdup (wfullpath); | |
| } | |
| else | |
| { | |
| wbasename_long = wcsrchr (wlongname, L'\\'); | |
| monitor->wfilename_long = wbasename_long != NULL ? | |
| wcsdup (wbasename_long + 1) : | |
| wcsdup (wlongname); | |
| } | |
| if (GetShortPathNameW (monitor->wfullpath_with_long_prefix, wshortname, MAX_PATH_LONG) == 0) | |
| { | |
| wbasename_short = wcsrchr (monitor->wfullpath_with_long_prefix, L'\\'); | |
| monitor->wfilename_short = wbasename_short != NULL ? | |
| wcsdup (wbasename_short + 1) : | |
| wcsdup (wfullpath); | |
| } | |
| else | |
| { | |
| wbasename_short = wcsrchr (wshortname, L'\\'); | |
| monitor->wfilename_short = wbasename_short != NULL ? | |
| wcsdup (wbasename_short + 1) : | |
| wcsdup (wshortname); | |
| } | |
| g_free (wfullpath); | |
| g_free (fullpath); | |
| } | |
| else | |
| { | |
| monitor->wfilename_short = NULL; | |
| monitor->wfilename_long = NULL; | |
| monitor->wfullpath_with_long_prefix = g_utf8_to_utf16 (dirname_with_long_prefix, -1, NULL, NULL, NULL); | |
| } | |
| monitor->isfile = isfile; | |
| } | |
| else | |
| { | |
| dirname_with_long_prefix = g_strconcat (LONGPFX, filename, NULL); | |
| monitor->wfullpath_with_long_prefix = g_utf8_to_utf16 (dirname_with_long_prefix, -1, NULL, NULL, NULL); | |
| monitor->wfilename_long = NULL; | |
| monitor->wfilename_short = NULL; | |
| monitor->isfile = FALSE; | |
| } | |
| success_attribs = GetFileAttributesExW (monitor->wfullpath_with_long_prefix, | |
| GetFileExInfoStandard, | |
| &attrib_data); | |
| if (success_attribs) | |
| monitor->file_attribs = attrib_data.dwFileAttributes; /* Store up original attributes */ | |
| else | |
| monitor->file_attribs = INVALID_FILE_ATTRIBUTES; | |
| monitor->pfni_prev = NULL; | |
| monitor->hDirectory = CreateFileW (wdirname_with_long_prefix != NULL ? wdirname_with_long_prefix : monitor->wfullpath_with_long_prefix, | |
| FILE_LIST_DIRECTORY, | |
| FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, | |
| NULL, | |
| OPEN_EXISTING, | |
| FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, | |
| NULL); | |
| g_free (wdirname_with_long_prefix); | |
| g_free (dirname_with_long_prefix); | |
| if (monitor->hDirectory != INVALID_HANDLE_VALUE) | |
| { | |
| ReadDirectoryChangesW (monitor->hDirectory, | |
| monitor->file_notify_buffer, | |
| monitor->buffer_allocated_bytes, | |
| FALSE, | |
| notify_filter, | |
| &monitor->buffer_filled_bytes, | |
| &monitor->overlapped, | |
| g_win32_fs_monitor_callback); | |
| } | |
| } | |
| GWin32FSMonitorPrivate * | |
| g_win32_fs_monitor_create (gboolean isfile) | |
| { | |
| GWin32FSMonitorPrivate *monitor = g_new0 (GWin32FSMonitorPrivate, 1); | |
| monitor->buffer_allocated_bytes = 32784; | |
| monitor->file_notify_buffer = g_new0 (FILE_NOTIFY_INFORMATION, monitor->buffer_allocated_bytes); | |
| return monitor; | |
| } | |
| void | |
| g_win32_fs_monitor_finalize (GWin32FSMonitorPrivate *monitor) | |
| { | |
| g_free (monitor->wfullpath_with_long_prefix); | |
| g_free (monitor->wfilename_long); | |
| g_free (monitor->wfilename_short); | |
| if (monitor->hDirectory == INVALID_HANDLE_VALUE) | |
| { | |
| /* If we don't have a directory handle we can free | |
| * monitor->file_notify_buffer and monitor here. The | |
| * callback won't be called obviously any more (and presumably | |
| * never has been called). | |
| */ | |
| g_free (monitor->file_notify_buffer); | |
| monitor->file_notify_buffer = NULL; | |
| g_free (monitor); | |
| } | |
| else | |
| { | |
| /* If we have a directory handle, the OVERLAPPED struct is | |
| * passed once more to the callback as a result of the | |
| * CloseHandle() done in the cancel method, so monitor has to | |
| * be kept around. The GWin32DirectoryMonitor object is | |
| * disappearing, so can't leave a pointer to it in | |
| * monitor->self. | |
| */ | |
| monitor->self = NULL; | |
| } | |
| } | |
| void | |
| g_win32_fs_monitor_close_handle (GWin32FSMonitorPrivate *monitor) | |
| { | |
| /* This triggers a last callback() with nBytes==0. */ | |
| /* Actually I am not so sure about that, it seems to trigger a last | |
| * callback correctly, but the way to recognize that it is the final | |
| * one is not to check for nBytes==0, I think that was a | |
| * misunderstanding. | |
| */ | |
| if (monitor->hDirectory != INVALID_HANDLE_VALUE) | |
| CloseHandle (monitor->hDirectory); | |
| } |