| /* 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 = -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 != -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) |
| { |
| gint long_filename_length = wcslen (monitor->wfilename_long); |
| gint 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 (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_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_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 allright, 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); |
| } |