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