Check for filespec when generating events in wxFileSystemWatcher.
[wxWidgets.git] / src / unix / fswatcher_inotify.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/unix/fswatcher_inotify.cpp
3 // Purpose: inotify-based wxFileSystemWatcher implementation
4 // Author: Bartosz Bekier
5 // Created: 2009-05-26
6 // RCS-ID: $Id$
7 // Copyright: (c) 2009 Bartosz Bekier <bartosz.bekier@gmail.com>
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
10
11 // For compilers that support precompilation, includes "wx.h".
12 #include "wx/wxprec.h"
13
14 #ifdef __BORLANDC__
15 #pragma hdrstop
16 #endif
17
18 #if wxUSE_FSWATCHER
19
20 #include "wx/fswatcher.h"
21
22 #ifdef wxHAS_INOTIFY
23
24 #include <sys/inotify.h>
25 #include <unistd.h>
26 #include "wx/private/fswatcher.h"
27
28 // ============================================================================
29 // wxFSWatcherImpl implementation & helper wxFSWSourceHandler implementation
30 // ============================================================================
31
32 // inotify watch descriptor => wxFSWatchEntry* map
33 WX_DECLARE_HASH_MAP(int, wxFSWatchEntry*, wxIntegerHash, wxIntegerEqual,
34 wxFSWatchEntryDescriptors);
35
36 // inotify event cookie => inotify_event* map
37 WX_DECLARE_HASH_MAP(int, inotify_event*, wxIntegerHash, wxIntegerEqual,
38 wxInotifyCookies);
39
40 /**
41 * Helper class encapsulating inotify mechanism
42 */
43 class wxFSWatcherImplUnix : public wxFSWatcherImpl
44 {
45 public:
46 wxFSWatcherImplUnix(wxFileSystemWatcherBase* watcher) :
47 wxFSWatcherImpl(watcher),
48 m_source(NULL),
49 m_ifd(-1)
50 {
51 m_handler = new wxFSWSourceHandler(this);
52 }
53
54 ~wxFSWatcherImplUnix()
55 {
56 // we close inotify only if initialized before
57 if (IsOk())
58 {
59 Close();
60 }
61
62 delete m_handler;
63 }
64
65 bool Init()
66 {
67 wxCHECK_MSG( !IsOk(), false, "Inotify already initialized" );
68
69 wxEventLoopBase *loop = wxEventLoopBase::GetActive();
70 wxCHECK_MSG( loop, false, "File system watcher needs an event loop" );
71
72 m_ifd = inotify_init();
73 if ( m_ifd == -1 )
74 {
75 wxLogSysError( _("Unable to create inotify instance") );
76 return false;
77 }
78
79 m_source = loop->AddSourceForFD
80 (
81 m_ifd,
82 m_handler,
83 wxEVENT_SOURCE_INPUT | wxEVENT_SOURCE_EXCEPTION
84 );
85
86 return m_source != NULL;
87 }
88
89 void Close()
90 {
91 wxCHECK_RET( IsOk(),
92 "Inotify not initialized or invalid inotify descriptor" );
93
94 wxDELETE(m_source);
95
96 if ( close(m_ifd) != 0 )
97 {
98 wxLogSysError( _("Unable to close inotify instance") );
99 }
100 }
101
102 virtual bool DoAdd(wxSharedPtr<wxFSWatchEntryUnix> watch)
103 {
104 wxCHECK_MSG( IsOk(), false,
105 "Inotify not initialized or invalid inotify descriptor" );
106
107 int wd = DoAddInotify(watch.get());
108 if (wd == -1)
109 {
110 wxLogSysError( _("Unable to add inotify watch") );
111 return false;
112 }
113
114 wxFSWatchEntryDescriptors::value_type val(wd, watch.get());
115 if (!m_watchMap.insert(val).second)
116 {
117 wxFAIL_MSG( wxString::Format( "Path %s is already watched",
118 watch->GetPath()) );
119 return false;
120 }
121
122 return true;
123 }
124
125 virtual bool DoRemove(wxSharedPtr<wxFSWatchEntryUnix> watch)
126 {
127 wxCHECK_MSG( IsOk(), false,
128 "Inotify not initialized or invalid inotify descriptor" );
129
130 int ret = DoRemoveInotify(watch.get());
131 if (ret == -1)
132 {
133 wxLogSysError( _("Unable to remove inotify watch") );
134 return false;
135 }
136
137 if (m_watchMap.erase(watch->GetWatchDescriptor()) != 1)
138 {
139 wxFAIL_MSG( wxString::Format("Path %s is not watched",
140 watch->GetPath()) );
141 }
142 // Cache the wd in case any events arrive late
143 m_staleDescriptors.Add(watch->GetWatchDescriptor());
144
145 watch->SetWatchDescriptor(-1);
146 return true;
147 }
148
149 virtual bool RemoveAll()
150 {
151 wxFSWatchEntries::iterator it = m_watches.begin();
152 for ( ; it != m_watches.end(); ++it )
153 {
154 (void) DoRemove(it->second);
155 }
156 m_watches.clear();
157 return true;
158 }
159
160 int ReadEvents()
161 {
162 wxCHECK_MSG( IsOk(), -1,
163 "Inotify not initialized or invalid inotify descriptor" );
164
165 // read events
166 // TODO differentiate depending on params
167 char buf[128 * sizeof(inotify_event)];
168 int left = ReadEventsToBuf(buf, sizeof(buf));
169 if (left == -1)
170 return -1;
171
172 // left > 0, we have events
173 char* memory = buf;
174 int event_count = 0;
175 while (left > 0) // OPT checking 'memory' would suffice
176 {
177 event_count++;
178 inotify_event* e = (inotify_event*)memory;
179
180 // process one inotify_event
181 ProcessNativeEvent(*e);
182
183 int offset = sizeof(inotify_event) + e->len;
184 left -= offset;
185 memory += offset;
186 }
187
188 // take care of unmatched renames
189 ProcessRenames();
190
191 wxLogTrace(wxTRACE_FSWATCHER, "We had %d native events", event_count);
192 return event_count;
193 }
194
195 bool IsOk() const
196 {
197 return m_source != NULL;
198 }
199
200 protected:
201 int DoAddInotify(wxFSWatchEntry* watch)
202 {
203 int flags = Watcher2NativeFlags(watch->GetFlags());
204 int wd = inotify_add_watch(m_ifd, watch->GetPath().fn_str(), flags);
205 // finally we can set watch descriptor
206 watch->SetWatchDescriptor(wd);
207 return wd;
208 }
209
210 int DoRemoveInotify(wxFSWatchEntry* watch)
211 {
212 return inotify_rm_watch(m_ifd, watch->GetWatchDescriptor());
213 }
214
215 void ProcessNativeEvent(const inotify_event& inevt)
216 {
217 wxLogTrace(wxTRACE_FSWATCHER, InotifyEventToString(inevt));
218
219 // after removing inotify watch we get IN_IGNORED for it, but the watch
220 // will be already removed from our list at that time
221 if (inevt.mask & IN_IGNORED)
222 {
223 // It is now safe to remove it from the stale descriptors too, we
224 // won't get any more events for it.
225 m_staleDescriptors.Remove(inevt.wd);
226 wxLogTrace(wxTRACE_FSWATCHER,
227 "Removed wd %i from the stale-wd cache", inevt.wd);
228 return;
229 }
230
231 // get watch entry for this event
232 wxFSWatchEntryDescriptors::iterator it = m_watchMap.find(inevt.wd);
233 if (it == m_watchMap.end())
234 {
235 // It's not in the map; check if was recently removed from it.
236 if (m_staleDescriptors.Index(inevt.wd) != wxNOT_FOUND)
237 {
238 wxLogTrace(wxTRACE_FSWATCHER,
239 "Got an event for stale wd %i", inevt.wd);
240 }
241 else
242 {
243 wxFAIL_MSG("Event for unknown watch descriptor.");
244 }
245
246 // In any case, don't process this event: it's either for an
247 // already removed entry, or for a completely unknown one.
248 return;
249 }
250
251 wxFSWatchEntry& watch = *(it->second);
252 int nativeFlags = inevt.mask;
253 int flags = Native2WatcherFlags(nativeFlags);
254
255 // check out for error/warning condition
256 if (flags & wxFSW_EVENT_WARNING || flags & wxFSW_EVENT_ERROR)
257 {
258 wxString errMsg = GetErrorDescription(Watcher2NativeFlags(flags));
259 wxFileSystemWatcherEvent event(flags, errMsg);
260 SendEvent(event);
261 }
262 // filter out ignored events and those not asked for.
263 // we never filter out warnings or exceptions
264 else if ((flags == 0) || !(flags & watch.GetFlags()))
265 {
266 return;
267 }
268 // renames
269 else if (nativeFlags & IN_MOVE)
270 {
271 wxInotifyCookies::iterator it2 = m_cookies.find(inevt.cookie);
272 if ( it2 == m_cookies.end() )
273 {
274 int size = sizeof(inevt) + inevt.len;
275 inotify_event* e = (inotify_event*) operator new (size);
276 memcpy(e, &inevt, size);
277
278 wxInotifyCookies::value_type val(e->cookie, e);
279 m_cookies.insert(val);
280 }
281 else
282 {
283 inotify_event& oldinevt = *(it2->second);
284
285 // Tell the owner, in case it's interested
286 // If there's a filespec, assume he's not
287 if ( watch.GetFilespec().empty() )
288 {
289 wxFileSystemWatcherEvent event(flags);
290 if ( inevt.mask & IN_MOVED_FROM )
291 {
292 event.SetPath(GetEventPath(watch, inevt));
293 event.SetNewPath(GetEventPath(watch, oldinevt));
294 }
295 else
296 {
297 event.SetPath(GetEventPath(watch, oldinevt));
298 event.SetNewPath(GetEventPath(watch, inevt));
299 }
300 SendEvent(event);
301 }
302
303 m_cookies.erase(it2);
304 delete &oldinevt;
305 }
306 }
307 // every other kind of event
308 else
309 {
310 wxFileName path = GetEventPath(watch, inevt);
311 // For files, check that it matches any filespec
312 if ( MatchesFilespec(path, watch.GetFilespec()) )
313 {
314 wxFileSystemWatcherEvent event(flags, path, path);
315 SendEvent(event);
316 }
317 }
318 }
319
320 void ProcessRenames()
321 {
322 wxInotifyCookies::iterator it = m_cookies.begin();
323 while ( it != m_cookies.end() )
324 {
325 inotify_event& inevt = *(it->second);
326
327 wxLogTrace(wxTRACE_FSWATCHER, "Processing pending rename events");
328 wxLogTrace(wxTRACE_FSWATCHER, InotifyEventToString(inevt));
329
330 // get watch entry for this event
331 wxFSWatchEntryDescriptors::iterator wit = m_watchMap.find(inevt.wd);
332 wxCHECK_RET(wit != m_watchMap.end(),
333 "Watch descriptor not present in the watch map!");
334
335 // Tell the owner, in case it's interested
336 // If there's a filespec, assume he's not
337 wxFSWatchEntry& watch = *(wit->second);
338 if ( watch.GetFilespec().empty() )
339 {
340 int flags = Native2WatcherFlags(inevt.mask);
341 wxFileName path = GetEventPath(watch, inevt);
342 {
343 wxFileSystemWatcherEvent event(flags, path, path);
344 SendEvent(event);
345 }
346 }
347
348 m_cookies.erase(it);
349 delete &inevt;
350 it = m_cookies.begin();
351 }
352 }
353
354 void SendEvent(wxFileSystemWatcherEvent& evt)
355 {
356 wxLogTrace(wxTRACE_FSWATCHER, evt.ToString());
357 m_watcher->GetOwner()->ProcessEvent(evt);
358 }
359
360 int ReadEventsToBuf(char* buf, int size)
361 {
362 wxCHECK_MSG( IsOk(), false,
363 "Inotify not initialized or invalid inotify descriptor" );
364
365 memset(buf, 0, size);
366 ssize_t left = read(m_ifd, buf, size);
367 if (left == -1)
368 {
369 wxLogSysError(_("Unable to read from inotify descriptor"));
370 return -1;
371 }
372 else if (left == 0)
373 {
374 wxLogWarning(_("EOF while reading from inotify descriptor"));
375 return -1;
376 }
377
378 return left;
379 }
380
381 static wxString InotifyEventToString(const inotify_event& inevt)
382 {
383 wxString mask = (inevt.mask & IN_ISDIR) ?
384 wxString::Format("IS_DIR | %u", inevt.mask & ~IN_ISDIR) :
385 wxString::Format("%u", inevt.mask);
386 const char* name = "";
387 if (inevt.len)
388 name = inevt.name;
389 return wxString::Format("Event: wd=%d, mask=%s, cookie=%u, len=%u, "
390 "name=%s", inevt.wd, mask, inevt.cookie,
391 inevt.len, name);
392 }
393
394 static wxFileName GetEventPath(const wxFSWatchEntry& watch,
395 const inotify_event& inevt)
396 {
397 // only when dir is watched, we have non-empty e.name
398 wxFileName path = watch.GetPath();
399 if (path.IsDir() && inevt.len)
400 {
401 path = wxFileName(path.GetPath(), inevt.name);
402 }
403 return path;
404 }
405
406 static int Watcher2NativeFlags(int WXUNUSED(flags))
407 {
408 // TODO: it would be nice to subscribe only to the events we really need
409 return IN_ALL_EVENTS;
410 }
411
412 static int Native2WatcherFlags(int flags)
413 {
414 static const int flag_mapping[][2] = {
415 { IN_ACCESS, wxFSW_EVENT_ACCESS }, // generated during read!
416 { IN_MODIFY, wxFSW_EVENT_MODIFY },
417 { IN_ATTRIB, 0 },
418 { IN_CLOSE_WRITE, 0 },
419 { IN_CLOSE_NOWRITE, 0 },
420 { IN_OPEN, 0 },
421 { IN_MOVED_FROM, wxFSW_EVENT_RENAME },
422 { IN_MOVED_TO, wxFSW_EVENT_RENAME },
423 { IN_CREATE, wxFSW_EVENT_CREATE },
424 { IN_DELETE, wxFSW_EVENT_DELETE },
425 { IN_DELETE_SELF, wxFSW_EVENT_DELETE },
426 { IN_MOVE_SELF, wxFSW_EVENT_DELETE },
427
428 { IN_UNMOUNT, wxFSW_EVENT_ERROR },
429 { IN_Q_OVERFLOW, wxFSW_EVENT_WARNING},
430
431 // ignored, because this is genereted mainly by watcher::Remove()
432 { IN_IGNORED, 0 }
433 };
434
435 unsigned int i=0;
436 for ( ; i < WXSIZEOF(flag_mapping); ++i) {
437 // in this mapping multiple flags at once don't happen
438 if (flags & flag_mapping[i][0])
439 return flag_mapping[i][1];
440 }
441
442 // never reached
443 wxFAIL_MSG(wxString::Format("Unknown inotify event mask %u", flags));
444 return -1;
445 }
446
447 /**
448 * Returns error description for specified inotify mask
449 */
450 static const wxString GetErrorDescription(int flag)
451 {
452 switch ( flag )
453 {
454 case IN_UNMOUNT:
455 return _("File system containing watched object was unmounted");
456 case IN_Q_OVERFLOW:
457 return _("Event queue overflowed");
458 }
459
460 // never reached
461 wxFAIL_MSG(wxString::Format("Unknown inotify event mask %u", flag));
462 return wxEmptyString;
463 }
464
465 wxFSWSourceHandler* m_handler; // handler for inotify event source
466 wxFSWatchEntryDescriptors m_watchMap; // inotify wd=>wxFSWatchEntry* map
467 wxArrayInt m_staleDescriptors; // stores recently-removed watches
468 wxInotifyCookies m_cookies; // map to track renames
469 wxEventLoopSource* m_source; // our event loop source
470
471 // file descriptor created by inotify_init()
472 int m_ifd;
473 };
474
475
476 // ============================================================================
477 // wxFSWSourceHandler implementation
478 // ============================================================================
479
480 // once we get signaled to read, actuall event reading occurs
481 void wxFSWSourceHandler::OnReadWaiting()
482 {
483 wxLogTrace(wxTRACE_FSWATCHER, "--- OnReadWaiting ---");
484 m_service->ReadEvents();
485 }
486
487 void wxFSWSourceHandler::OnWriteWaiting()
488 {
489 wxFAIL_MSG("We never write to inotify descriptor.");
490 }
491
492 void wxFSWSourceHandler::OnExceptionWaiting()
493 {
494 wxFAIL_MSG("We never receive exceptions on inotify descriptor.");
495 }
496
497
498 // ============================================================================
499 // wxInotifyFileSystemWatcher implementation
500 // ============================================================================
501
502 wxInotifyFileSystemWatcher::wxInotifyFileSystemWatcher()
503 : wxFileSystemWatcherBase()
504 {
505 Init();
506 }
507
508 wxInotifyFileSystemWatcher::wxInotifyFileSystemWatcher(const wxFileName& path,
509 int events)
510 : wxFileSystemWatcherBase()
511 {
512 if (!Init())
513 {
514 if (m_service)
515 delete m_service;
516 return;
517 }
518
519 Add(path, events);
520 }
521
522 wxInotifyFileSystemWatcher::~wxInotifyFileSystemWatcher()
523 {
524 }
525
526 bool wxInotifyFileSystemWatcher::Init()
527 {
528 m_service = new wxFSWatcherImplUnix(this);
529 return m_service->Init();
530 }
531
532 #endif // wxHAS_INOTIFY
533
534 #endif // wxUSE_FSWATCHER