Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
wdir_watch.cpp
Go to the documentation of this file.
1 /* Copyright (c) 2010 Wildfire Games
2  *
3  * Permission is hereby granted, free of charge, to any person obtaining
4  * a copy of this software and associated documentation files (the
5  * "Software"), to deal in the Software without restriction, including
6  * without limitation the rights to use, copy, modify, merge, publish,
7  * distribute, sublicense, and/or sell copies of the Software, and to
8  * permit persons to whom the Software is furnished to do so, subject to
9  * the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included
12  * in all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  */
22 
23 /*
24  * Win32 directory change notification
25  */
26 
27 #include "precompiled.h"
28 #include "lib/sysdep/dir_watch.h"
29 
31 #include "lib/path.h" // path_is_subpath
32 #include "lib/sysdep/os/win/win.h"
36 
37 
40 
41 
42 //-----------------------------------------------------------------------------
43 // DirHandle
44 
45 class DirHandle
46 {
47 public:
48  DirHandle(const OsPath& path)
49  {
50  WinScopedPreserveLastError s; // CreateFile
51  const DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
52  const DWORD flags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
53  m_hDir = CreateFileW(OsString(path).c_str(), FILE_LIST_DIRECTORY, share, 0, OPEN_EXISTING, flags, 0);
54  }
55 
57  {
58  // contrary to MSDN, the canceled IOs do not issue a completion notification.
59  // (receiving packets after (unsuccessful) cancellation would be dangerous)
60  BOOL ok = CancelIo(m_hDir);
61  WARN_IF_FALSE(ok);
62 
63  CloseHandle(m_hDir);
64  m_hDir = INVALID_HANDLE_VALUE;
65  }
66 
67  // == INVALID_HANDLE_VALUE if path doesn't exist
68  operator HANDLE() const
69  {
70  return m_hDir;
71  }
72 
73 private:
75 };
76 
77 
78 //-----------------------------------------------------------------------------
79 // DirWatchRequest
80 
82 {
84 public:
85  DirWatchRequest(const OsPath& path)
86  : m_path(path), m_dirHandle(path), m_data(new u8[dataSize])
87  {
88  m_ovl = (OVERLAPPED*)calloc(1, sizeof(OVERLAPPED)); // rationale for dynamic alloc: see decl
89  ENSURE(m_ovl);
90 
91  // (hEvent is needed for the wait after CancelIo below)
92  const BOOL manualReset = TRUE;
93  const BOOL initialState = FALSE;
94  m_ovl->hEvent = CreateEvent(0, manualReset, initialState, 0);
95  }
96 
98  {
99  // we need to free m_data here, so the pending IO had better
100  // not write to that memory in future. therefore:
101  WARN_IF_FALSE(CancelIo(m_dirHandle));
102  // however, this is not synchronized with the DPC (?) that apparently
103  // delivers the data - m_data is filled anyway.
104  // we need to ensure that either the IO has happened or that it
105  // was successfully canceled before freeing m_data and m_ovl, so wait:
106  {
108  // (GetOverlappedResult without a valid hEvent hangs on Vista;
109  // we'll abort after a timeout to be safe.)
110  const DWORD ret = WaitForSingleObject(m_ovl->hEvent, 1000);
111  WARN_IF_FALSE(CloseHandle(m_ovl->hEvent));
112  if(ret == WAIT_OBJECT_0 || GetLastError() == ERROR_OPERATION_ABORTED)
113  {
114  SetLastError(0);
115  delete[] m_data;
116  free(m_ovl);
117  }
118  else
119  {
120  // (this could conceivably happen if a kernel debugger
121  // hangs the system during the wait duration.)
122  debug_printf(L"WARNING: IO may still be pending; to avoid memory corruption, we won't free the buffer.\n");
124  // intentionally leak m_data and m_ovl!
125  }
126  }
127  }
128 
129  const OsPath& Path() const
130  {
131  return m_path;
132  }
133 
134  void AttachTo(HANDLE& hIOCP) const
135  {
136  AttachToCompletionPort(m_dirHandle, hIOCP, (uintptr_t)this);
137  }
138 
139  // (called again after each notification, so it mustn't AttachToCompletionPort)
141  {
142  if(m_dirHandle == INVALID_HANDLE_VALUE)
144 
145  const BOOL watchSubtree = TRUE; // (see IntrusiveLink comments)
146  const DWORD filter = FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME |
147  FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE |
148  FILE_NOTIFY_CHANGE_CREATION;
149  // not set: FILE_NOTIFY_CHANGE_ATTRIBUTES, FILE_NOTIFY_CHANGE_LAST_ACCESS, FILE_NOTIFY_CHANGE_SECURITY
150  DWORD undefined = 0; // (non-NULL pointer avoids BoundsChecker warning)
151  m_ovl->Internal = 0;
152  WARN_IF_FALSE(ReadDirectoryChangesW(m_dirHandle, m_data, dataSize, watchSubtree, filter, &undefined, m_ovl, 0));
153  return INFO::OK;
154  }
155 
156  /**
157  * (call when completion port indicates data is available)
158  **/
159  void RetrieveNotifications(DirWatchNotifications& notifications) const
160  {
161  const FILE_NOTIFY_INFORMATION* fni = (const FILE_NOTIFY_INFORMATION*)m_data;
162  for(;;)
163  {
164  // convert (non-zero-terminated) BSTR to Path::String
165  cassert(sizeof(wchar_t) == sizeof(WCHAR));
166  const size_t length = fni->FileNameLength / sizeof(WCHAR);
167  Path::String name(fni->FileName, length);
168 
169  // (NB: name is actually a relative path since we watch entire subtrees)
170  const OsPath pathname = m_path / name;
171  const DirWatchNotification::EType type = TypeFromAction(fni->Action);
172  notifications.push_back(DirWatchNotification(pathname, type));
173 
174  if(!fni->NextEntryOffset) // this was the last entry.
175  break;
176  fni = (const FILE_NOTIFY_INFORMATION*)(uintptr_t(fni) + fni->NextEntryOffset);
177  }
178  }
179 
180 private:
182  {
183  switch(action)
184  {
185  case FILE_ACTION_ADDED:
186  case FILE_ACTION_RENAMED_NEW_NAME:
188 
189  case FILE_ACTION_REMOVED:
190  case FILE_ACTION_RENAMED_OLD_NAME:
192 
193  case FILE_ACTION_MODIFIED:
195 
196  default:
199  }
200  }
201 
204 
205  // rationale:
206  // - if too small, notifications may be lost! (the CSD-poll application
207  // may be confronted with hundreds of new files in a short time frame)
208  // - requests larger than 64 KiB fail on SMB due to packet restrictions.
209  static const size_t dataSize = 64*KiB;
210 
211  // rationale:
212  // - each instance needs their own buffer. (we can't share a central
213  // copy because the watches are independent and may be triggered
214  // 'simultaneously' before the next poll.)
215  // - lifetime must be managed manually (see dtor)
217 
218  // rationale:
219  // - ReadDirectoryChangesW's asynchronous mode is triggered by passing
220  // a valid OVERLAPPED parameter; notification proceeds via
221  // completion ports, but we still need hEvent - see above.
222  // - this must remain valid while the IO is pending. if the wait
223  // were to fail, we must not free this memory, either.
224  OVERLAPPED* m_ovl;
225 };
226 
227 typedef shared_ptr<DirWatchRequest> PDirWatchRequest;
228 
229 
230 //-----------------------------------------------------------------------------
231 // IntrusiveLink
232 
233 // using watches of entire subtrees to satisfy single-directory requests
234 // requires a list of existing watches. an intrusive, doubly-linked list
235 // is convenient because removal must occur within the DirWatch destructor.
236 // since boost::intrusive doesn't automatically remove objects from their
237 // containers when they are destroyed, we implement a simple circular list
238 // via sentinel. note that DirWatchManager iterates over DirWatch, not their
239 // embedded links. we map from link to the parent object via offsetof
240 // (slightly less complex than storing back pointers to the parents, and
241 // avoids 'this-pointer used during initialization list' warnings).
242 
244 {
245 public:
247  {
248  m_prev = m_next = this; // sentinel
249  }
250 
252  {
253  // insert after sentinel
254  m_prev = sentinel;
255  m_next = sentinel->m_next;
256  m_next->m_prev = this;
257  sentinel->m_next = this;
258  }
259 
261  {
262  // remove from list
263  m_prev->m_next = m_next;
264  m_next->m_prev = m_prev;
265  }
266 
268  {
269  return m_next;
270  }
271 
272 private:
275 };
276 
277 
278 //-----------------------------------------------------------------------------
279 // DirWatch
280 
281 struct DirWatch
282 {
284  : link(sentinel), request(request)
285  {
286  }
287 
290 };
291 
292 
293 //-----------------------------------------------------------------------------
294 // DirWatchManager
295 
297 {
298 public:
300  : hIOCP(0) // Win32 requires 0-init; created in the first call to AttachTo
301  {
302  }
303 
305  {
306  CloseHandle(hIOCP);
307  }
308 
309  Status Add(const OsPath& path, PDirWatch& dirWatch)
310  {
311  ENSURE(path.IsDirectory());
312 
313  // check if this is a subdirectory of a tree that's already being
314  // watched (this is much faster than issuing a new watch; it also
315  // prevents accidentally watching the same directory twice).
316  for(IntrusiveLink* link = m_sentinel.Next(); link != &m_sentinel; link = link->Next())
317  {
318  DirWatch* const existingDirWatch = (DirWatch*)(uintptr_t(link) - offsetof(DirWatch, link));
319  if(path_is_subpath(OsString(path).c_str(), OsString(existingDirWatch->request->Path()).c_str()))
320  {
321  dirWatch.reset(new DirWatch(&m_sentinel, existingDirWatch->request));
322  return INFO::OK;
323  }
324  }
325 
326  PDirWatchRequest request(new DirWatchRequest(path));
327  request->AttachTo(hIOCP);
328  RETURN_STATUS_IF_ERR(request->Issue());
329  dirWatch.reset(new DirWatch(&m_sentinel, request));
330  return INFO::OK;
331  }
332 
334  {
335 POLL_AGAIN:
336  DWORD bytesTransferred; ULONG_PTR key; OVERLAPPED* ovl;
337  const Status ret = PollCompletionPort(hIOCP, 0, bytesTransferred, key, ovl);
338  if(ret == ERR::ABORTED) // watch was canceled
339  goto POLL_AGAIN;
341 
342  DirWatchRequest* request = (DirWatchRequest*)key;
343  request->RetrieveNotifications(notifications);
344  RETURN_STATUS_IF_ERR(request->Issue()); // re-issue
345  return INFO::OK;
346  }
347 
348 private:
351 };
352 
354 
355 
356 //-----------------------------------------------------------------------------
357 
358 Status dir_watch_Add(const OsPath& path, PDirWatch& dirWatch)
359 {
361  return s_dirWatchManager->Add(path, dirWatch);
362 }
363 
365 {
367  return s_dirWatchManager->Poll(notifications);
368 }
369 
370 
371 //-----------------------------------------------------------------------------
372 
374 {
375  s_dirWatchManager = new DirWatchManager;
376  return INFO::OK;
377 }
378 
380 {
381  SAFE_DELETE(s_dirWatchManager);
382  return INFO::OK;
383 }
#define u8
Definition: types.h:39
const Status LOGIC
Definition: status.h:409
std::vector< DirWatchNotification > DirWatchNotifications
Definition: dir_watch.h:84
static const size_t dataSize
Definition: wdir_watch.cpp:209
shared_ptr< DirWatchRequest > PDirWatchRequest
Definition: wdir_watch.cpp:227
const Status OK
Definition: status.h:386
some WinAPI functions SetLastError(0) on success, which is bad because it can hide previous errors...
Definition: wutil.h:119
bool IsDirectory() const
Definition: path.h:143
DirHandle(const OsPath &path)
Definition: wdir_watch.cpp:48
const Status TIMED_OUT
Definition: status.h:411
const OsPath & Path() const
Definition: wdir_watch.cpp:129
OVERLAPPED * m_ovl
Definition: wdir_watch.cpp:224
LIB_API Status dir_watch_Add(const OsPath &path, PDirWatch &dirWatch)
start watching a single directory for changes.
Definition: dir_watch.cpp:28
#define WINIT_REGISTER_MAIN_SHUTDOWN(func)
Definition: winit.h:156
Status Add(const OsPath &path, PDirWatch &dirWatch)
Definition: wdir_watch.cpp:309
void AttachToCompletionPort(HANDLE hFile, HANDLE &hIOCP, ULONG_PTR key, DWORD numConcurrentThreads)
Definition: wiocp.cpp:8
int BOOL
Definition: wgl.h:51
NONCOPYABLE(DirWatchRequest)
void AttachTo(HANDLE &hIOCP) const
Definition: wdir_watch.cpp:134
const Status ABORTED
Definition: status.h:414
HANDLE m_hDir
Definition: wdir_watch.cpp:74
#define ENSURE(expr)
ensure the expression &lt;expr&gt; evaluates to non-zero.
Definition: debug.h:282
Status Poll(DirWatchNotifications &notifications)
Definition: wdir_watch.cpp:333
shared_ptr< DirWatch > PDirWatch
Definition: dir_watch.h:32
Definition: path.h:75
void * HANDLE
Definition: wgl.h:62
LIB_API Status dir_watch_Poll(DirWatchNotifications &notifications)
return all pending directory watch notifications.
Definition: dir_watch.cpp:33
std::wstring String
Definition: path.h:78
Status PollCompletionPort(HANDLE hIOCP, DWORD timeout, DWORD &bytesTransferred, ULONG_PTR &key, OVERLAPPED *&ovl)
Definition: wiocp.cpp:18
static const size_t KiB
Definition: alignment.h:71
unsigned long DWORD
Definition: wgl.h:56
pthread_key_t key
Definition: wpthread.cpp:140
#define SAFE_DELETE(p)
delete memory ensuing from new and set the pointer to zero (thus making double-frees safe / a no-op) ...
PDirWatchRequest request
Definition: wdir_watch.cpp:289
i64 Status
Error handling system.
Definition: status.h:171
IntrusiveLink m_sentinel
Definition: wdir_watch.cpp:349
static DirWatchNotification::EType TypeFromAction(const DWORD action)
Definition: wdir_watch.cpp:181
void RetrieveNotifications(DirWatchNotifications &notifications) const
(call when completion port indicates data is available)
Definition: wdir_watch.cpp:159
DirWatchRequest(const OsPath &path)
Definition: wdir_watch.cpp:85
#define DEBUG_WARN_ERR(status)
display the error dialog with text corresponding to the given error code.
Definition: debug.h:331
IntrusiveLink link
Definition: wdir_watch.cpp:288
bool path_is_subpath(const wchar_t *s1, const wchar_t *s2)
is s2 a subpath of s1, or vice versa? (equal counts as subpath)
Definition: path.cpp:51
static HANDLE hIOCP
Definition: waio.cpp:54
#define WINIT_REGISTER_MAIN_INIT(func)
Definition: winit.h:148
DirWatch(IntrusiveLink *sentinel, const PDirWatchRequest &request)
Definition: wdir_watch.cpp:283
static Status wdir_watch_Init()
Definition: wdir_watch.cpp:373
DirHandle m_dirHandle
Definition: wdir_watch.cpp:203
#define WARN_IF_FALSE(expression)
Definition: status.h:360
#define WARN_RETURN(status)
Definition: status.h:255
static Status wdir_watch_Shutdown()
Definition: wdir_watch.cpp:379
#define cassert(expr)
Compile-time assertion.
static DirWatchManager * s_dirWatchManager
Definition: wdir_watch.cpp:353
void debug_printf(const wchar_t *fmt,...)
write a formatted string to the debug channel, subject to filtering (see below).
Definition: debug.cpp:142
const Status PATH_NOT_FOUND
Definition: path.h:50
static std::string OsString(const OsPath &path)
Definition: os_path.h:42
#define RETURN_STATUS_IF_ERR(expression)
Definition: status.h:276