1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/unix/fswatcher_inotify.cpp
3 // Purpose: inotify-based wxFileSystemWatcher implementation
4 // Author: Bartosz Bekier
7 // Copyright: (c) 2009 Bartosz Bekier <bartosz.bekier@gmail.com>
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
11 // For compilers that support precompilation, includes "wx.h".
12 #include "wx/wxprec.h"
20 #include "wx/fswatcher.h"
24 #include <sys/inotify.h>
26 #include "wx/private/fswatcher.h"
28 // ============================================================================
29 // wxFSWatcherImpl implementation & helper wxFSWSourceHandler implementation
30 // ============================================================================
32 // inotify watch descriptor => wxFSWatchEntry* map
33 WX_DECLARE_HASH_MAP(int, wxFSWatchEntry
*, wxIntegerHash
, wxIntegerEqual
,
34 wxFSWatchEntryDescriptors
);
36 // inotify event cookie => inotify_event* map
37 WX_DECLARE_HASH_MAP(int, inotify_event
*, wxIntegerHash
, wxIntegerEqual
,
41 * Helper class encapsulating inotify mechanism
43 class wxFSWatcherImplUnix
: public wxFSWatcherImpl
46 wxFSWatcherImplUnix(wxFileSystemWatcherBase
* watcher
) :
47 wxFSWatcherImpl(watcher
),
51 m_handler
= new wxFSWSourceHandler(this);
54 ~wxFSWatcherImplUnix()
56 // we close inotify only if initialized before
67 wxCHECK_MSG( !IsOk(), false, "Inotify already initialized" );
69 wxEventLoopBase
*loop
= wxEventLoopBase
::GetActive();
70 wxCHECK_MSG( loop
, false, "File system watcher needs an event loop" );
72 m_ifd
= inotify_init();
75 wxLogSysError( _("Unable to create inotify instance") );
79 m_source
= loop
->AddSourceForFD
83 wxEVENT_SOURCE_INPUT
| wxEVENT_SOURCE_EXCEPTION
86 return m_source
!= NULL
;
92 "Inotify not initialized or invalid inotify descriptor" );
96 if ( close(m_ifd
) != 0 )
98 wxLogSysError( _("Unable to close inotify instance") );
102 virtual bool DoAdd(wxSharedPtr
<wxFSWatchEntryUnix
> watch
)
104 wxCHECK_MSG( IsOk(), false,
105 "Inotify not initialized or invalid inotify descriptor" );
107 int wd
= DoAddInotify(watch
.get());
110 wxLogSysError( _("Unable to add inotify watch") );
114 wxFSWatchEntryDescriptors
::value_type
val(wd
, watch
.get());
115 if (!m_watchMap
.insert(val
).second
)
117 wxFAIL_MSG( wxString
::Format( "Path %s is already watched",
125 virtual bool DoRemove(wxSharedPtr
<wxFSWatchEntryUnix
> watch
)
127 wxCHECK_MSG( IsOk(), false,
128 "Inotify not initialized or invalid inotify descriptor" );
130 int ret
= DoRemoveInotify(watch
.get());
133 wxLogSysError( _("Unable to remove inotify watch") );
137 if (m_watchMap
.erase(watch
->GetWatchDescriptor()) != 1)
139 wxFAIL_MSG( wxString
::Format("Path %s is not watched",
142 // Cache the wd in case any events arrive late
143 m_staleDescriptors
.Add(watch
->GetWatchDescriptor());
145 watch
->SetWatchDescriptor(-1);
149 virtual bool RemoveAll()
151 wxFSWatchEntries
::iterator it
= m_watches
.begin();
152 for ( ; it
!= m_watches
.end(); ++it
)
154 (void) DoRemove(it
->second
);
162 wxCHECK_MSG( IsOk(), -1,
163 "Inotify not initialized or invalid inotify descriptor" );
166 // TODO differentiate depending on params
167 char buf
[128 * sizeof(inotify_event
)];
168 int left
= ReadEventsToBuf(buf
, sizeof(buf
));
172 // left > 0, we have events
175 while (left
> 0) // OPT checking 'memory' would suffice
178 inotify_event
* e
= (inotify_event
*)memory
;
180 // process one inotify_event
181 ProcessNativeEvent(*e
);
183 int offset
= sizeof(inotify_event
) + e
->len
;
188 // take care of unmatched renames
191 wxLogTrace(wxTRACE_FSWATCHER
, "We had %d native events", event_count
);
197 return m_source
!= NULL
;
201 int DoAddInotify(wxFSWatchEntry
* watch
)
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
);
210 int DoRemoveInotify(wxFSWatchEntry
* watch
)
212 return inotify_rm_watch(m_ifd
, watch
->GetWatchDescriptor());
215 void ProcessNativeEvent(const inotify_event
& inevt
)
217 wxLogTrace(wxTRACE_FSWATCHER
, InotifyEventToString(inevt
));
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
)
223 // It is now safe to remove it from the stale descriptors too, we
224 // won't get any more events for it.
225 // However if we're here because a dir that we're still watching
226 // has just been deleted, its wd won't be on this list
227 const int pos
= m_staleDescriptors
.Index(inevt
.wd
);
228 if ( pos
!= wxNOT_FOUND
)
230 m_staleDescriptors
.RemoveAt(static_cast<size_t>(pos
));
231 wxLogTrace(wxTRACE_FSWATCHER
,
232 "Removed wd %i from the stale-wd cache", inevt
.wd
);
237 // get watch entry for this event
238 wxFSWatchEntryDescriptors
::iterator it
= m_watchMap
.find(inevt
.wd
);
240 // wd will be -1 for IN_Q_OVERFLOW, which would trigger the wxFAIL_MSG
243 if (it
== m_watchMap
.end())
245 // It's not in the map; check if was recently removed from it.
246 if (m_staleDescriptors
.Index(inevt
.wd
) != wxNOT_FOUND
)
248 wxLogTrace(wxTRACE_FSWATCHER
,
249 "Got an event for stale wd %i", inevt
.wd
);
253 wxFAIL_MSG("Event for unknown watch descriptor.");
256 // In any case, don't process this event: it's either for an
257 // already removed entry, or for a completely unknown one.
262 int nativeFlags
= inevt
.mask
;
263 int flags
= Native2WatcherFlags(nativeFlags
);
265 // check out for error/warning condition
266 if (flags
& wxFSW_EVENT_WARNING
|| flags
& wxFSW_EVENT_ERROR
)
268 wxString errMsg
= GetErrorDescription(nativeFlags
);
269 wxFileSystemWatcherEvent
event(flags
, errMsg
);
274 wxFSWatchEntry
& watch
= *(it
->second
);
276 // Now IN_UNMOUNT. We must do so here, as it's not in the watch flags
277 if (nativeFlags
& IN_UNMOUNT
)
279 wxFileName path
= GetEventPath(watch
, inevt
);
280 wxFileSystemWatcherEvent
event(wxFSW_EVENT_UNMOUNT
, path
, path
);
283 // filter out ignored events and those not asked for.
284 // we never filter out warnings or exceptions
285 else if ((flags
== 0) || !(flags
& watch
.GetFlags()))
291 // We need do something here only if the original watch was recursive;
292 // we don't watch a child dir itself inside a non-tree watch.
293 // We watch only dirs explicitly, so we don't want file IN_CREATEs.
294 // Distinguish by whether nativeFlags contain IN_ISDIR
295 else if ((nativeFlags
& IN_CREATE
) &&
296 (watch
.GetType() == wxFSWPath_Tree
) && (inevt
.mask
& IN_ISDIR
))
298 wxFileName fn
= GetEventPath(watch
, inevt
);
299 // Though it's a dir, fn treats it as a file. So:
300 fn
.AssignDir(fn
.GetFullPath());
302 if (m_watcher
->AddAny(fn
, wxFSW_EVENT_ALL
,
303 wxFSWPath_Tree
, watch
.GetFilespec()))
305 // Tell the owner, in case it's interested
306 // If there's a filespec, assume he's not
307 if (watch
.GetFilespec().empty())
309 wxFileSystemWatcherEvent
event(flags
, fn
, fn
);
316 // We watch only dirs explicitly, so we don't want file IN_DELETEs.
317 // We obviously can't check using DirExists() as the object has been
318 // deleted; and nativeFlags here doesn't contain IN_ISDIR, even for
319 // a dir. Fortunately IN_DELETE_SELF doesn't happen for files. We need
320 // to do something here only inside a tree watch, or if it's the parent
321 // dir that's deleted. Otherwise let the parent dir cope
322 else if ((nativeFlags
& IN_DELETE_SELF
) &&
323 ((watch
.GetType() == wxFSWPath_Dir
) ||
324 (watch
.GetType() == wxFSWPath_Tree
)))
326 // We must remove the deleted directory from the map, so that
327 // DoRemoveInotify() isn't called on it in the future. Don't assert
328 // if the wd isn't found: repeated IN_DELETE_SELFs can occur
329 wxFileName fn
= GetEventPath(watch
, inevt
);
330 wxString
path(fn
.GetPathWithSep());
332 if (m_watchMap
.erase(inevt
.wd
) == 1)
334 // Delete from wxFileSystemWatcher
335 wxDynamicCast(m_watcher
, wxInotifyFileSystemWatcher
)->
338 // Now remove from our local list of watched items
339 wxFSWatchEntries
::iterator wit
=
340 m_watches
.find(path
);
341 if (wit
!= m_watches
.end())
343 m_watches
.erase(wit
);
346 // Cache the wd in case any events arrive late
347 m_staleDescriptors
.Add(inevt
.wd
);
350 // Tell the owner, in case it's interested
351 // If there's a filespec, assume he's not
352 if (watch
.GetFilespec().empty())
354 wxFileSystemWatcherEvent
event(flags
, fn
, fn
);
360 else if (nativeFlags
& IN_MOVE
)
362 wxInotifyCookies
::iterator it2
= m_cookies
.find(inevt
.cookie
);
363 if ( it2
== m_cookies
.end() )
365 int size
= sizeof(inevt
) + inevt
.len
;
366 inotify_event
* e
= (inotify_event
*) operator new (size
);
367 memcpy(e
, &inevt
, size
);
369 wxInotifyCookies
::value_type
val(e
->cookie
, e
);
370 m_cookies
.insert(val
);
374 inotify_event
& oldinevt
= *(it2
->second
);
376 // Tell the owner, in case it's interested
377 // If there's a filespec, assume he's not
378 if ( watch
.GetFilespec().empty() )
380 wxFileSystemWatcherEvent
event(flags
);
381 if ( inevt
.mask
& IN_MOVED_FROM
)
383 event
.SetPath(GetEventPath(watch
, inevt
));
384 event
.SetNewPath(GetEventPath(watch
, oldinevt
));
388 event
.SetPath(GetEventPath(watch
, oldinevt
));
389 event
.SetNewPath(GetEventPath(watch
, inevt
));
394 m_cookies
.erase(it2
);
398 // every other kind of event
401 wxFileName path
= GetEventPath(watch
, inevt
);
402 // For files, check that it matches any filespec
403 if ( MatchesFilespec(path
, watch
.GetFilespec()) )
405 wxFileSystemWatcherEvent
event(flags
, path
, path
);
411 void ProcessRenames()
413 wxInotifyCookies
::iterator it
= m_cookies
.begin();
414 while ( it
!= m_cookies
.end() )
416 inotify_event
& inevt
= *(it
->second
);
418 wxLogTrace(wxTRACE_FSWATCHER
, "Processing pending rename events");
419 wxLogTrace(wxTRACE_FSWATCHER
, InotifyEventToString(inevt
));
421 // get watch entry for this event
422 wxFSWatchEntryDescriptors
::iterator wit
= m_watchMap
.find(inevt
.wd
);
423 wxCHECK_RET(wit
!= m_watchMap
.end(),
424 "Watch descriptor not present in the watch map!");
426 // Tell the owner, in case it's interested
427 // If there's a filespec, assume he's not
428 wxFSWatchEntry
& watch
= *(wit
->second
);
429 if ( watch
.GetFilespec().empty() )
431 int flags
= Native2WatcherFlags(inevt
.mask
);
432 wxFileName path
= GetEventPath(watch
, inevt
);
434 wxFileSystemWatcherEvent
event(flags
, path
, path
);
441 it
= m_cookies
.begin();
445 void SendEvent(wxFileSystemWatcherEvent
& evt
)
447 wxLogTrace(wxTRACE_FSWATCHER
, evt
.ToString());
448 m_watcher
->GetOwner()->ProcessEvent(evt
);
451 int ReadEventsToBuf(char* buf
, int size
)
453 wxCHECK_MSG( IsOk(), false,
454 "Inotify not initialized or invalid inotify descriptor" );
456 memset(buf
, 0, size
);
457 ssize_t left
= read(m_ifd
, buf
, size
);
460 wxLogSysError(_("Unable to read from inotify descriptor"));
465 wxLogWarning(_("EOF while reading from inotify descriptor"));
472 static wxString
InotifyEventToString(const inotify_event
& inevt
)
474 wxString mask
= (inevt
.mask
& IN_ISDIR
) ?
475 wxString
::Format("IS_DIR | %u", inevt
.mask
& ~IN_ISDIR
) :
476 wxString
::Format("%u", inevt
.mask
);
477 const char* name
= "";
480 return wxString
::Format("Event: wd=%d, mask=%s, cookie=%u, len=%u, "
481 "name=%s", inevt
.wd
, mask
, inevt
.cookie
,
485 static wxFileName
GetEventPath(const wxFSWatchEntry
& watch
,
486 const inotify_event
& inevt
)
488 // only when dir is watched, we have non-empty e.name
489 wxFileName path
= watch
.GetPath();
490 if (path
.IsDir() && inevt
.len
)
492 path
= wxFileName(path
.GetPath(), inevt
.name
);
497 static int Watcher2NativeFlags(int flags
)
499 // Start with the standard case of wanting all events
500 if (flags
== wxFSW_EVENT_ALL
)
502 return IN_ALL_EVENTS
;
505 static const int flag_mapping
[][2] = {
506 { wxFSW_EVENT_ACCESS
, IN_ACCESS
},
507 { wxFSW_EVENT_MODIFY
, IN_MODIFY
},
508 { wxFSW_EVENT_ATTRIB
, IN_ATTRIB
},
509 { wxFSW_EVENT_RENAME
, IN_MOVE
},
510 { wxFSW_EVENT_CREATE
, IN_CREATE
},
511 { wxFSW_EVENT_DELETE
, IN_DELETE
|IN_DELETE_SELF
|IN_MOVE_SELF
},
512 { wxFSW_EVENT_UNMOUNT
, IN_UNMOUNT
}
513 // wxFSW_EVENT_ERROR/WARNING make no sense here
516 int native_flags
= 0;
517 for ( unsigned int i
=0; i
< WXSIZEOF(flag_mapping
); ++i
)
519 if (flags
& flag_mapping
[i
][0])
520 native_flags
|= flag_mapping
[i
][1];
526 static int Native2WatcherFlags(int flags
)
528 static const int flag_mapping
[][2] = {
529 { IN_ACCESS
, wxFSW_EVENT_ACCESS
}, // generated during read!
530 { IN_MODIFY
, wxFSW_EVENT_MODIFY
},
531 { IN_ATTRIB
, wxFSW_EVENT_ATTRIB
},
532 { IN_CLOSE_WRITE
, 0 },
533 { IN_CLOSE_NOWRITE
, 0 },
535 { IN_MOVED_FROM
, wxFSW_EVENT_RENAME
},
536 { IN_MOVED_TO
, wxFSW_EVENT_RENAME
},
537 { IN_CREATE
, wxFSW_EVENT_CREATE
},
538 { IN_DELETE
, wxFSW_EVENT_DELETE
},
539 { IN_DELETE_SELF
, wxFSW_EVENT_DELETE
},
540 { IN_MOVE_SELF
, wxFSW_EVENT_DELETE
},
542 { IN_UNMOUNT
, wxFSW_EVENT_UNMOUNT
},
543 { IN_Q_OVERFLOW
, wxFSW_EVENT_WARNING
},
545 // ignored, because this is generated mainly by watcher::Remove()
550 for ( ; i
< WXSIZEOF(flag_mapping
); ++i
) {
551 // in this mapping multiple flags at once don't happen
552 if (flags
& flag_mapping
[i
][0])
553 return flag_mapping
[i
][1];
557 wxFAIL_MSG(wxString
::Format("Unknown inotify event mask %u", flags
));
562 * Returns error description for specified inotify mask
564 static const wxString
GetErrorDescription(int flag
)
569 return _("Event queue overflowed");
573 wxFAIL_MSG(wxString
::Format("Unknown inotify event mask %u", flag
));
574 return wxEmptyString
;
577 wxFSWSourceHandler
* m_handler
; // handler for inotify event source
578 wxFSWatchEntryDescriptors m_watchMap
; // inotify wd=>wxFSWatchEntry* map
579 wxArrayInt m_staleDescriptors
; // stores recently-removed watches
580 wxInotifyCookies m_cookies
; // map to track renames
581 wxEventLoopSource
* m_source
; // our event loop source
583 // file descriptor created by inotify_init()
588 // ============================================================================
589 // wxFSWSourceHandler implementation
590 // ============================================================================
592 // once we get signaled to read, actuall event reading occurs
593 void wxFSWSourceHandler
::OnReadWaiting()
595 wxLogTrace(wxTRACE_FSWATCHER
, "--- OnReadWaiting ---");
596 m_service
->ReadEvents();
599 void wxFSWSourceHandler
::OnWriteWaiting()
601 wxFAIL_MSG("We never write to inotify descriptor.");
604 void wxFSWSourceHandler
::OnExceptionWaiting()
606 wxFAIL_MSG("We never receive exceptions on inotify descriptor.");
610 // ============================================================================
611 // wxInotifyFileSystemWatcher implementation
612 // ============================================================================
614 wxInotifyFileSystemWatcher
::wxInotifyFileSystemWatcher()
615 : wxFileSystemWatcherBase()
620 wxInotifyFileSystemWatcher
::wxInotifyFileSystemWatcher(const wxFileName
& path
,
622 : wxFileSystemWatcherBase()
634 wxInotifyFileSystemWatcher
::~wxInotifyFileSystemWatcher()
638 bool wxInotifyFileSystemWatcher
::Init()
640 m_service
= new wxFSWatcherImplUnix(this);
641 return m_service
->Init();
644 void wxInotifyFileSystemWatcher
::OnDirDeleted(const wxString
& path
)
648 wxFSWatchInfoMap
::iterator it
= m_watches
.find(path
);
649 wxCHECK_RET(it
!= m_watches
.end(),
650 wxString
::Format("Path '%s' is not watched", path
));
652 // path has been deleted, so we must forget it whatever its refcount
657 #endif // wxHAS_INOTIFY
659 #endif // wxUSE_FSWATCHER