1
/////////////////////////////////////////////////////////////////////////////
2
// Name: src/unix/fswatcher_inotify.cpp
3
// Purpose: inotify-based wxFileSystemWatcher implementation
4
// Author: Bartosz Bekier
6
// Copyright: (c) 2009 Bartosz Bekier <bartosz.bekier@gmail.com>
7
// Licence: wxWindows licence
8
/////////////////////////////////////////////////////////////////////////////
10
// For compilers that support precompilation, includes "wx.h".
11
#include "wx/wxprec.h"
19
#include "wx/fswatcher.h"
23
#include <sys/inotify.h>
25
#include "wx/private/fswatcher.h"
27
// ============================================================================
28
// wxFSWatcherImpl implementation & helper wxFSWSourceHandler implementation
29
// ============================================================================
31
// inotify watch descriptor => wxFSWatchEntry* map
32
WX_DECLARE_HASH_MAP(int, wxFSWatchEntry*, wxIntegerHash, wxIntegerEqual,
33
wxFSWatchEntryDescriptors);
35
// inotify event cookie => inotify_event* map
36
WX_DECLARE_HASH_MAP(int, inotify_event*, wxIntegerHash, wxIntegerEqual,
40
* Helper class encapsulating inotify mechanism
42
class wxFSWatcherImplUnix : public wxFSWatcherImpl
45
wxFSWatcherImplUnix(wxFileSystemWatcherBase* watcher) :
46
wxFSWatcherImpl(watcher),
50
m_handler = new wxFSWSourceHandler(this);
53
~wxFSWatcherImplUnix()
55
// we close inotify only if initialized before
66
wxCHECK_MSG( !IsOk(), false, "Inotify already initialized" );
68
wxEventLoopBase *loop = wxEventLoopBase::GetActive();
69
wxCHECK_MSG( loop, false, "File system watcher needs an event loop" );
71
m_ifd = inotify_init();
74
wxLogSysError( _("Unable to create inotify instance") );
78
m_source = loop->AddSourceForFD
82
wxEVENT_SOURCE_INPUT | wxEVENT_SOURCE_EXCEPTION
85
return m_source != NULL;
91
"Inotify not initialized or invalid inotify descriptor" );
95
if ( close(m_ifd) != 0 )
97
wxLogSysError( _("Unable to close inotify instance") );
101
virtual bool DoAdd(wxSharedPtr<wxFSWatchEntryUnix> watch)
103
wxCHECK_MSG( IsOk(), false,
104
"Inotify not initialized or invalid inotify descriptor" );
106
int wd = DoAddInotify(watch.get());
109
wxLogSysError( _("Unable to add inotify watch") );
113
wxFSWatchEntryDescriptors::value_type val(wd, watch.get());
114
if (!m_watchMap.insert(val).second)
116
wxFAIL_MSG( wxString::Format( "Path %s is already watched",
124
virtual bool DoRemove(wxSharedPtr<wxFSWatchEntryUnix> watch)
126
wxCHECK_MSG( IsOk(), false,
127
"Inotify not initialized or invalid inotify descriptor" );
129
int ret = DoRemoveInotify(watch.get());
132
wxLogSysError( _("Unable to remove inotify watch") );
136
if (m_watchMap.erase(watch->GetWatchDescriptor()) != 1)
138
wxFAIL_MSG( wxString::Format("Path %s is not watched",
141
// Cache the wd in case any events arrive late
142
m_staleDescriptors.Add(watch->GetWatchDescriptor());
144
watch->SetWatchDescriptor(-1);
148
virtual bool RemoveAll()
150
wxFSWatchEntries::iterator it = m_watches.begin();
151
for ( ; it != m_watches.end(); ++it )
153
(void) DoRemove(it->second);
161
wxCHECK_MSG( IsOk(), -1,
162
"Inotify not initialized or invalid inotify descriptor" );
165
// TODO differentiate depending on params
166
char buf[128 * sizeof(inotify_event)];
167
int left = ReadEventsToBuf(buf, sizeof(buf));
171
// left > 0, we have events
174
while (left > 0) // OPT checking 'memory' would suffice
177
inotify_event* e = (inotify_event*)memory;
179
// process one inotify_event
180
ProcessNativeEvent(*e);
182
int offset = sizeof(inotify_event) + e->len;
187
// take care of unmatched renames
190
wxLogTrace(wxTRACE_FSWATCHER, "We had %d native events", event_count);
196
return m_source != NULL;
200
int DoAddInotify(wxFSWatchEntry* watch)
202
int flags = Watcher2NativeFlags(watch->GetFlags());
203
int wd = inotify_add_watch(m_ifd, watch->GetPath().fn_str(), flags);
204
// finally we can set watch descriptor
205
watch->SetWatchDescriptor(wd);
209
int DoRemoveInotify(wxFSWatchEntry* watch)
211
return inotify_rm_watch(m_ifd, watch->GetWatchDescriptor());
214
void ProcessNativeEvent(const inotify_event& inevt)
216
wxLogTrace(wxTRACE_FSWATCHER, InotifyEventToString(inevt));
218
// after removing inotify watch we get IN_IGNORED for it, but the watch
219
// will be already removed from our list at that time
220
if (inevt.mask & IN_IGNORED)
222
// It is now safe to remove it from the stale descriptors too, we
223
// won't get any more events for it.
224
// However if we're here because a dir that we're still watching
225
// has just been deleted, its wd won't be on this list
226
const int pos = m_staleDescriptors.Index(inevt.wd);
227
if ( pos != wxNOT_FOUND )
229
m_staleDescriptors.RemoveAt(static_cast<size_t>(pos));
230
wxLogTrace(wxTRACE_FSWATCHER,
231
"Removed wd %i from the stale-wd cache", inevt.wd);
236
// get watch entry for this event
237
wxFSWatchEntryDescriptors::iterator it = m_watchMap.find(inevt.wd);
239
// wd will be -1 for IN_Q_OVERFLOW, which would trigger the wxFAIL_MSG
242
if (it == m_watchMap.end())
244
// It's not in the map; check if was recently removed from it.
245
if (m_staleDescriptors.Index(inevt.wd) != wxNOT_FOUND)
247
wxLogTrace(wxTRACE_FSWATCHER,
248
"Got an event for stale wd %i", inevt.wd);
252
// In theory we shouldn't reach here. In practice, some
253
// events, e.g. IN_MODIFY, arrive just after the IN_IGNORED
254
// so their wd has already been discarded. Warn about them.
255
wxFileSystemWatcherEvent
261
_("Unexpected event for \"%s\": no "
262
"matching watch descriptor."),
263
inevt.len ? inevt.name : ""
270
// In any case, don't process this event: it's either for an
271
// already removed entry, or for an unknown one.
276
int nativeFlags = inevt.mask;
277
int flags = Native2WatcherFlags(nativeFlags);
279
// check out for error/warning condition
280
if (flags & wxFSW_EVENT_WARNING || flags & wxFSW_EVENT_ERROR)
282
wxString errMsg = GetErrorDescription(nativeFlags);
283
wxFileSystemWatcherEvent event(flags, errMsg);
290
// Although this is not supposed to happen, we seem to be getting
291
// occasional IN_ACCESS/IN_MODIFY events without valid watch value.
292
wxFileSystemWatcherEvent
298
_("Invalid inotify event for \"%s\""),
299
inevt.len ? inevt.name : ""
306
wxFSWatchEntry& watch = *(it->second);
308
// Now IN_UNMOUNT. We must do so here, as it's not in the watch flags
309
if (nativeFlags & IN_UNMOUNT)
311
wxFileName path = GetEventPath(watch, inevt);
312
wxFileSystemWatcherEvent event(wxFSW_EVENT_UNMOUNT, path, path);
315
// filter out ignored events and those not asked for.
316
// we never filter out warnings or exceptions
317
else if ((flags == 0) || !(flags & watch.GetFlags()))
323
// We need do something here only if the original watch was recursive;
324
// we don't watch a child dir itself inside a non-tree watch.
325
// We watch only dirs explicitly, so we don't want file IN_CREATEs.
326
// Distinguish by whether nativeFlags contain IN_ISDIR
327
else if ((nativeFlags & IN_CREATE) &&
328
(watch.GetType() == wxFSWPath_Tree) && (inevt.mask & IN_ISDIR))
330
wxFileName fn = GetEventPath(watch, inevt);
331
// Though it's a dir, fn treats it as a file. So:
332
fn.AssignDir(fn.GetFullPath());
334
if (m_watcher->AddAny(fn, wxFSW_EVENT_ALL,
335
wxFSWPath_Tree, watch.GetFilespec()))
337
// Tell the owner, in case it's interested
338
// If there's a filespec, assume he's not
339
if (watch.GetFilespec().empty())
341
wxFileSystemWatcherEvent event(flags, fn, fn);
348
// We watch only dirs explicitly, so we don't want file IN_DELETEs.
349
// We obviously can't check using DirExists() as the object has been
350
// deleted; and nativeFlags here doesn't contain IN_ISDIR, even for
351
// a dir. Fortunately IN_DELETE_SELF doesn't happen for files. We need
352
// to do something here only inside a tree watch, or if it's the parent
353
// dir that's deleted. Otherwise let the parent dir cope
354
else if ((nativeFlags & IN_DELETE_SELF) &&
355
((watch.GetType() == wxFSWPath_Dir) ||
356
(watch.GetType() == wxFSWPath_Tree)))
358
// We must remove the deleted directory from the map, so that
359
// DoRemoveInotify() isn't called on it in the future. Don't assert
360
// if the wd isn't found: repeated IN_DELETE_SELFs can occur
361
wxFileName fn = GetEventPath(watch, inevt);
362
wxString path(fn.GetPathWithSep());
364
if (m_watchMap.erase(inevt.wd) == 1)
366
// Delete from wxFileSystemWatcher
367
wxDynamicCast(m_watcher, wxInotifyFileSystemWatcher)->
370
// Now remove from our local list of watched items
371
wxFSWatchEntries::iterator wit =
372
m_watches.find(path);
373
if (wit != m_watches.end())
375
m_watches.erase(wit);
378
// Cache the wd in case any events arrive late
379
m_staleDescriptors.Add(inevt.wd);
382
// Tell the owner, in case it's interested
383
// If there's a filespec, assume he's not
384
if (watch.GetFilespec().empty())
386
wxFileSystemWatcherEvent event(flags, fn, fn);
392
else if (nativeFlags & IN_MOVE)
394
// IN_MOVE events are produced in the following circumstances:
395
// * A move within a dir (what the user sees as a rename) gives an
396
// IN_MOVED_FROM and IN_MOVED_TO pair, each with the same path.
397
// * A move within watched dir foo/ e.g. from foo/bar1/ to foo/bar2/
398
// will also produce such a pair, but with different paths.
399
// * A move to baz/ will give only an IN_MOVED_FROM for foo/bar1;
400
// if baz/ is inside a different watch, that gets the IN_MOVED_TO.
402
// The first event to arrive, usually the IN_MOVED_FROM, is
403
// cached to await a matching IN_MOVED_TO; should none arrive, the
404
// unpaired event will be processed later in ProcessRenames().
405
wxInotifyCookies::iterator it2 = m_cookies.find(inevt.cookie);
406
if ( it2 == m_cookies.end() )
408
int size = sizeof(inevt) + inevt.len;
409
inotify_event* e = (inotify_event*) operator new (size);
410
memcpy(e, &inevt, size);
412
wxInotifyCookies::value_type val(e->cookie, e);
413
m_cookies.insert(val);
417
inotify_event& oldinevt = *(it2->second);
419
// Tell the owner, in case it's interested
420
// If there's a filespec, assume he's not
421
if ( watch.GetFilespec().empty() )
423
// The the only way to know the path for the first event,
424
// normally the IN_MOVED_FROM, is to retrieve the watch
425
// corresponding to oldinevt. This is needed for a move
427
wxFSWatchEntry* oldwatch;
428
wxFSWatchEntryDescriptors::iterator oldwatch_it =
429
m_watchMap.find(oldinevt.wd);
430
if (oldwatch_it != m_watchMap.end())
432
oldwatch = oldwatch_it->second;
436
wxLogTrace(wxTRACE_FSWATCHER,
437
"oldinevt's watch descriptor not in the watch map");
438
// For want of a better alternative, use 'watch'. That
439
// will work fine for renames, though not for moves
443
wxFileSystemWatcherEvent event(flags);
444
if ( inevt.mask & IN_MOVED_FROM )
446
event.SetPath(GetEventPath(watch, inevt));
447
event.SetNewPath(GetEventPath(*oldwatch, oldinevt));
451
event.SetPath(GetEventPath(*oldwatch, oldinevt));
452
event.SetNewPath(GetEventPath(watch, inevt));
457
m_cookies.erase(it2);
461
// every other kind of event
464
wxFileName path = GetEventPath(watch, inevt);
465
// For files, check that it matches any filespec
466
if ( MatchesFilespec(path, watch.GetFilespec()) )
468
wxFileSystemWatcherEvent event(flags, path, path);
474
void ProcessRenames()
476
// After all of a batch of events has been processed, this deals with
477
// any still-unpaired IN_MOVED_FROM or IN_MOVED_TO events.
478
wxInotifyCookies::iterator it = m_cookies.begin();
479
while ( it != m_cookies.end() )
481
inotify_event& inevt = *(it->second);
483
wxLogTrace(wxTRACE_FSWATCHER, "Processing pending rename events");
484
wxLogTrace(wxTRACE_FSWATCHER, InotifyEventToString(inevt));
486
// get watch entry for this event
487
wxFSWatchEntryDescriptors::iterator wit = m_watchMap.find(inevt.wd);
488
if (wit == m_watchMap.end())
490
wxLogTrace(wxTRACE_FSWATCHER,
491
"Watch descriptor not present in the watch map!");
495
// Tell the owner, in case it's interested
496
// If there's a filespec, assume he's not
497
wxFSWatchEntry& watch = *(wit->second);
498
if ( watch.GetFilespec().empty() )
500
int flags = Native2WatcherFlags(inevt.mask);
501
wxFileName path = GetEventPath(watch, inevt);
503
wxFileSystemWatcherEvent event(flags, path, path);
511
it = m_cookies.begin();
515
void SendEvent(wxFileSystemWatcherEvent& evt)
517
wxLogTrace(wxTRACE_FSWATCHER, evt.ToString());
518
m_watcher->GetOwner()->ProcessEvent(evt);
521
int ReadEventsToBuf(char* buf, int size)
523
wxCHECK_MSG( IsOk(), false,
524
"Inotify not initialized or invalid inotify descriptor" );
526
memset(buf, 0, size);
527
ssize_t left = read(m_ifd, buf, size);
530
wxLogSysError(_("Unable to read from inotify descriptor"));
535
wxLogWarning(_("EOF while reading from inotify descriptor"));
542
static wxString InotifyEventToString(const inotify_event& inevt)
544
wxString mask = (inevt.mask & IN_ISDIR) ?
545
wxString::Format("IS_DIR | %u", inevt.mask & ~IN_ISDIR) :
546
wxString::Format("%u", inevt.mask);
547
const char* name = "";
550
return wxString::Format("Event: wd=%d, mask=%s, cookie=%u, len=%u, "
551
"name=%s", inevt.wd, mask, inevt.cookie,
555
static wxFileName GetEventPath(const wxFSWatchEntry& watch,
556
const inotify_event& inevt)
558
// only when dir is watched, we have non-empty e.name
559
wxFileName path = watch.GetPath();
560
if (path.IsDir() && inevt.len)
562
path = wxFileName(path.GetPath(), inevt.name);
567
static int Watcher2NativeFlags(int flags)
569
// Start with the standard case of wanting all events
570
if (flags == wxFSW_EVENT_ALL)
572
return IN_ALL_EVENTS;
575
static const int flag_mapping[][2] = {
576
{ wxFSW_EVENT_ACCESS, IN_ACCESS },
577
{ wxFSW_EVENT_MODIFY, IN_MODIFY },
578
{ wxFSW_EVENT_ATTRIB, IN_ATTRIB },
579
{ wxFSW_EVENT_RENAME, IN_MOVE },
580
{ wxFSW_EVENT_CREATE, IN_CREATE },
581
{ wxFSW_EVENT_DELETE, IN_DELETE|IN_DELETE_SELF|IN_MOVE_SELF },
582
{ wxFSW_EVENT_UNMOUNT, IN_UNMOUNT }
583
// wxFSW_EVENT_ERROR/WARNING make no sense here
586
int native_flags = 0;
587
for ( unsigned int i=0; i < WXSIZEOF(flag_mapping); ++i)
589
if (flags & flag_mapping[i][0])
590
native_flags |= flag_mapping[i][1];
596
static int Native2WatcherFlags(int flags)
598
static const int flag_mapping[][2] = {
599
{ IN_ACCESS, wxFSW_EVENT_ACCESS }, // generated during read!
600
{ IN_MODIFY, wxFSW_EVENT_MODIFY },
601
{ IN_ATTRIB, wxFSW_EVENT_ATTRIB },
602
{ IN_CLOSE_WRITE, 0 },
603
{ IN_CLOSE_NOWRITE, 0 },
605
{ IN_MOVED_FROM, wxFSW_EVENT_RENAME },
606
{ IN_MOVED_TO, wxFSW_EVENT_RENAME },
607
{ IN_CREATE, wxFSW_EVENT_CREATE },
608
{ IN_DELETE, wxFSW_EVENT_DELETE },
609
{ IN_DELETE_SELF, wxFSW_EVENT_DELETE },
610
{ IN_MOVE_SELF, wxFSW_EVENT_DELETE },
612
{ IN_UNMOUNT, wxFSW_EVENT_UNMOUNT},
613
{ IN_Q_OVERFLOW, wxFSW_EVENT_WARNING},
615
// ignored, because this is generated mainly by watcher::Remove()
620
for ( ; i < WXSIZEOF(flag_mapping); ++i) {
621
// in this mapping multiple flags at once don't happen
622
if (flags & flag_mapping[i][0])
623
return flag_mapping[i][1];
627
wxFAIL_MSG(wxString::Format("Unknown inotify event mask %u", flags));
632
* Returns error description for specified inotify mask
634
static const wxString GetErrorDescription(int flag)
639
return _("Event queue overflowed");
643
wxFAIL_MSG(wxString::Format("Unknown inotify event mask %u", flag));
644
return wxEmptyString;
647
wxFSWSourceHandler* m_handler; // handler for inotify event source
648
wxFSWatchEntryDescriptors m_watchMap; // inotify wd=>wxFSWatchEntry* map
649
wxArrayInt m_staleDescriptors; // stores recently-removed watches
650
wxInotifyCookies m_cookies; // map to track renames
651
wxEventLoopSource* m_source; // our event loop source
653
// file descriptor created by inotify_init()
658
// ============================================================================
659
// wxFSWSourceHandler implementation
660
// ============================================================================
662
// once we get signaled to read, actuall event reading occurs
663
void wxFSWSourceHandler::OnReadWaiting()
665
wxLogTrace(wxTRACE_FSWATCHER, "--- OnReadWaiting ---");
666
m_service->ReadEvents();
669
void wxFSWSourceHandler::OnWriteWaiting()
671
wxFAIL_MSG("We never write to inotify descriptor.");
674
void wxFSWSourceHandler::OnExceptionWaiting()
676
wxFAIL_MSG("We never receive exceptions on inotify descriptor.");
680
// ============================================================================
681
// wxInotifyFileSystemWatcher implementation
682
// ============================================================================
684
wxInotifyFileSystemWatcher::wxInotifyFileSystemWatcher()
685
: wxFileSystemWatcherBase()
690
wxInotifyFileSystemWatcher::wxInotifyFileSystemWatcher(const wxFileName& path,
692
: wxFileSystemWatcherBase()
704
wxInotifyFileSystemWatcher::~wxInotifyFileSystemWatcher()
708
bool wxInotifyFileSystemWatcher::Init()
710
m_service = new wxFSWatcherImplUnix(this);
711
return m_service->Init();
714
void wxInotifyFileSystemWatcher::OnDirDeleted(const wxString& path)
718
wxFSWatchInfoMap::iterator it = m_watches.find(path);
719
wxCHECK_RET(it != m_watches.end(),
720
wxString::Format("Path '%s' is not watched", path));
722
// path has been deleted, so we must forget it whatever its refcount
727
#endif // wxHAS_INOTIFY
729
#endif // wxUSE_FSWATCHER