1 /* 2 * Copyright (c) 2014-2015 Enrico M. Crisostomo 3 * 4 * This program is free software; you can redistribute it and/or modify it under 5 * the terms of the GNU General Public License as published by the Free Software 6 * Foundation; either version 3, or (at your option) any later version. 7 * 8 * This program is distributed in the hope that it will be useful, but WITHOUT 9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 10 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 11 * details. 12 * 13 * You should have received a copy of the GNU General Public License along with 14 * this program. If not, see <http://www.gnu.org/licenses/>. 15 */ 16 #ifdef HAVE_CONFIG_H 17 # include "libfswatch_config.h" 18 #endif 19 20 #ifdef HAVE_WINDOWS 21 22 # include "gettext_defs.h" 23 # include "windows_monitor.hpp" 24 # include "libfswatch_map.hpp" 25 # include "libfswatch_set.hpp" 26 # include "libfswatch_exception.hpp" 27 # include "../c/libfswatch_log.h" 28 # include <algorithm> 29 # include <set> 30 # include <iostream> 31 # include <memory> 32 # include <sys/types.h> 33 # include <cstdlib> 34 # include <cstring> 35 # include <ctime> 36 # include <cstdio> 37 # include <unistd.h> 38 # include <fcntl.h> 39 # include <windows.h> 40 # include "./windows/win_handle.hpp" 41 # include "./windows/win_error_message.hpp" 42 # include "./windows/win_strings.hpp" 43 # include "./windows/win_paths.hpp" 44 # include "./windows/win_directory_change_event.hpp" 45 46 using namespace std; 47 48 namespace fsw 49 { 50 struct windows_monitor_load 51 { 52 fsw_hash_set<wstring> win_paths; 53 fsw_hash_map<wstring, directory_change_event> dce_by_path; 54 fsw_hash_map<wstring, win_handle> event_by_path; 55 long buffer_size = 128; 56 }; 57 windows_monitor(vector<string> paths_to_monitor,FSW_EVENT_CALLBACK * callback,void * context)58 windows_monitor::windows_monitor(vector<string> paths_to_monitor, 59 FSW_EVENT_CALLBACK * callback, 60 void * context) : 61 monitor(paths_to_monitor, callback, context), load(new windows_monitor_load()) 62 { 63 SetConsoleOutputCP(CP_UTF8); 64 } 65 ~windows_monitor()66 windows_monitor::~windows_monitor() 67 { 68 delete load; 69 } 70 initialize_windows_path_list()71 void windows_monitor::initialize_windows_path_list() 72 { 73 for (const auto & path : paths) 74 { 75 load->win_paths.insert(win_paths::posix_to_win_w(path)); 76 } 77 } 78 initialize_events()79 void windows_monitor::initialize_events() 80 { 81 for (const wstring & path : load->win_paths) 82 { 83 FSW_ELOGF(_("Creating event for %s.\n"), win_strings::wstring_to_string(path).c_str()); 84 85 HANDLE h = CreateEvent(nullptr, 86 TRUE, 87 FALSE, 88 nullptr); 89 90 if (h == NULL) throw libfsw_exception(_("CreateEvent failed.")); 91 92 FSW_ELOGF(_("Event %d created for %s.\n"), h, win_strings::wstring_to_string(path).c_str()); 93 94 load->event_by_path.emplace(path, h); 95 } 96 } 97 init_search_for_path(const wstring path)98 bool windows_monitor::init_search_for_path(const wstring path) 99 { 100 FSW_ELOGF(_("Initializing search structures for %s.\n"), win_strings::wstring_to_string(path).c_str()); 101 102 HANDLE h = CreateFileW(path.c_str(), 103 GENERIC_READ, 104 FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 105 nullptr, OPEN_EXISTING, 106 FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, 107 nullptr); 108 109 if (!win_handle::is_valid(h)) 110 { 111 fprintf(stderr, _("Invalid handle when opening %s.\n"), win_strings::wstring_to_string(path).c_str()); 112 return false; 113 } 114 115 FSW_ELOGF(_("Open file handle: %d.\n"), h); 116 117 directory_change_event dce(load->buffer_size); 118 dce.path = path; 119 dce.handle = h; 120 dce.overlapped.get()->hEvent = load->event_by_path[path]; 121 122 if (!dce.read_changes_async()) 123 { 124 FSW_ELOGF("ReadDirectoryChangesW: %s\n", win_strings::wstring_to_string(win_error_message::current()).c_str()); 125 return false; 126 } 127 128 load->dce_by_path[path] = move(dce); 129 130 return true; 131 } 132 stop_search_for_path(const wstring path)133 void windows_monitor::stop_search_for_path(const wstring path) 134 { 135 load->dce_by_path.erase(path); 136 } 137 is_path_watched(wstring path)138 bool windows_monitor::is_path_watched(wstring path) 139 { 140 return (load->dce_by_path.find(path) != load->dce_by_path.end()); 141 } 142 process_path(const wstring & path)143 void windows_monitor::process_path(const wstring & path) 144 { 145 FSW_ELOGF(_("Processing %s.\n"), win_strings::wstring_to_string(path).c_str()); 146 147 // If the path is not currently watched, then initialize the search 148 // structures. If the initalization fails, skip the path altogether 149 // until the next iteration. 150 if (!is_path_watched(path)) 151 { 152 if (!init_search_for_path(path)) return; 153 } 154 155 auto it = load->dce_by_path.find(path); 156 if (it == load->dce_by_path.end()) throw libfsw_exception(_("Initialization failed.")); 157 158 directory_change_event & dce = it->second; 159 160 if (!dce.try_read()) 161 { 162 if (dce.is_io_incomplete()) 163 { 164 FSW_ELOG(_("I/O incomplete.\n")); 165 return; 166 } 167 168 if (dce.is_buffer_overflowed()) 169 { 170 notify_overflow(win_paths::win_w_to_posix(path)); 171 } 172 173 stop_search_for_path(path); 174 175 return; 176 } 177 178 FSW_ELOGF(_("GetOverlappedResult returned %d bytes\n"), dce.bytes_returned); 179 180 if (dce.bytes_returned == 0) 181 { 182 notify_overflow(win_paths::win_w_to_posix(path)); 183 } 184 else 185 { 186 vector<event> events = dce.get_events(); 187 188 if (events.size()) notify_events(events); 189 } 190 191 if (!dce.read_changes_async()) 192 { 193 FSW_ELOGF(_("ReadDirectoryChangesW: %s\n"), win_strings::wstring_to_string(win_error_message::current()).c_str()); 194 stop_search_for_path(path); 195 } 196 } 197 configure_monitor()198 void windows_monitor::configure_monitor() 199 { 200 string buffer_size_value = get_property("windows.ReadDirectoryChangesW.buffer.size"); 201 202 if (buffer_size_value.empty()) return; 203 204 long parsed_value = strtol(buffer_size_value.c_str(), nullptr, 0); 205 206 if (parsed_value <= 0) 207 { 208 string msg = string(_("Invalid value: ")) + buffer_size_value; 209 throw libfsw_exception(msg.c_str()); 210 } 211 212 load->buffer_size = parsed_value; 213 } 214 run()215 void windows_monitor::run() 216 { 217 // Since the file handles are open with FILE_SHARE_DELETE, it may happen 218 // that file is deleted when a handle to it is being used. A call to 219 // either ReadDirectoryChangesW or GetOverlappedResult will return with 220 // an error if the file system object being observed is deleted. 221 // Unfortunately, the error reported by Windows is `Access denied', 222 // preventing fswatch to report better messages to the user. 223 224 configure_monitor(); 225 initialize_windows_path_list(); 226 initialize_events(); 227 228 for (;;) 229 { 230 #ifdef HAVE_CXX_MUTEX 231 unique_lock<mutex> run_guard(run_mutex); 232 if (should_stop) break; 233 run_guard.unlock(); 234 #endif 235 236 sleep(latency); 237 238 for (const auto & path : load->win_paths) 239 { 240 process_path(path); 241 } 242 } 243 } 244 } 245 246 #endif /* HAVE_WINDOWS */ 247