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