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" );
68 wxCHECK_MSG( m_loop
== NULL
, false, "Event loop != NULL");
70 m_loop
= (wxEventLoopBase::GetActive());
71 wxCHECK_MSG( m_loop
, false, "File system watcher needs an active loop" );
73 int fd
= inotify_init();
76 wxLogSysError( _("Unable to create inotify instance") );
80 int flags
= wxEVENT_SOURCE_INPUT
| wxEVENT_SOURCE_EXCEPTION
;
81 m_source
= static_cast<wxUnixEventLoopSource
*>(
82 m_loop
->CreateSource(fd
, m_handler
, flags
));
83 return RegisterSource();
88 wxCHECK_MSG( IsOk(), false,
89 "Inotify not initialized or invalid inotify descriptor" );
90 wxCHECK_MSG( m_loop
, false,
91 "m_loop shouldn't be null if inotify is initialized" );
94 (void) UnregisterSource();
96 int ret
= close(m_source
->GetResource());
99 wxLogSysError( _("Unable to close inotify instance") );
101 m_source
->Invalidate();
106 virtual bool DoAdd(wxSharedPtr
<wxFSWatchEntryUnix
> watch
)
108 wxCHECK_MSG( IsOk(), false,
109 "Inotify not initialized or invalid inotify descriptor" );
111 int wd
= DoAddInotify(watch
.get());
114 wxLogSysError( _("Unable to add inotify watch") );
118 wxFSWatchEntryDescriptors::value_type
val(wd
, watch
.get());
119 if (!m_watchMap
.insert(val
).second
)
121 wxFAIL_MSG( wxString::Format( "Path %s is already watched",
129 virtual bool DoRemove(wxSharedPtr
<wxFSWatchEntryUnix
> watch
)
131 wxCHECK_MSG( IsOk(), false,
132 "Inotify not initialized or invalid inotify descriptor" );
134 int ret
= DoRemoveInotify(watch
.get());
137 wxLogSysError( _("Unable to remove inotify watch") );
141 if (m_watchMap
.erase(watch
->GetWatchDescriptor()) != 1)
143 wxFAIL_MSG( wxString::Format("Path %s is not watched",
146 watch
->SetWatchDescriptor(-1);
150 virtual bool RemoveAll()
152 wxFSWatchEntries::iterator it
= m_watches
.begin();
153 for ( ; it
!= m_watches
.end(); ++it
)
155 (void) DoRemove(it
->second
);
163 wxCHECK_MSG( IsOk(), -1,
164 "Inotify not initialized or invalid inotify descriptor" );
167 // TODO differentiate depending on params
168 char buf
[128 * sizeof(inotify_event
)];
169 int left
= ReadEventsToBuf(buf
, sizeof(buf
));
173 // left > 0, we have events
176 while (left
> 0) // OPT checking 'memory' would suffice
179 inotify_event
* e
= (inotify_event
*)memory
;
181 // process one inotify_event
182 ProcessNativeEvent(*e
);
184 int offset
= sizeof(inotify_event
) + e
->len
;
189 // take care of unmatched renames
192 wxLogTrace(wxTRACE_FSWATCHER
, "We had %d native events", event_count
);
198 return m_source
&& m_source
->IsOk();
202 bool RegisterSource()
204 wxCHECK_MSG( IsOk(), false,
205 "Inotify not initialized or invalid inotify descriptor" );
207 bool ret
= m_loop
->AddSource(m_source
);
211 bool UnregisterSource()
213 wxCHECK_MSG( IsOk(), false,
214 "Inotify not initialized or invalid inotify descriptor" );
215 wxCHECK_MSG( m_loop
, false,
216 "m_loop shouldn't be null if inotify is initialized" );
218 bool ret
= m_loop
->RemoveSource(m_source
);
223 int DoAddInotify(wxFSWatchEntry
* watch
)
225 int flags
= Watcher2NativeFlags(watch
->GetFlags());
226 int wd
= inotify_add_watch(m_source
->GetResource(),
227 watch
->GetPath().fn_str(),
229 // finally we can set watch descriptor
230 watch
->SetWatchDescriptor(wd
);
234 int DoRemoveInotify(wxFSWatchEntry
* watch
)
236 return inotify_rm_watch(m_source
->GetResource(),
237 watch
->GetWatchDescriptor());
240 void ProcessNativeEvent(const inotify_event
& inevt
)
242 wxLogTrace(wxTRACE_FSWATCHER
, InotifyEventToString(inevt
));
244 // after removing inotify watch we get IN_IGNORED for it, but the watch
245 // will be already removed from our list at that time
246 if (inevt
.mask
& IN_IGNORED
)
251 // get watch entry for this event
252 wxFSWatchEntryDescriptors::iterator it
= m_watchMap
.find(inevt
.wd
);
253 wxCHECK_RET(it
!= m_watchMap
.end(),
254 "Watch descriptor not present in the watch map!");
256 wxFSWatchEntry
& watch
= *(it
->second
);
257 int nativeFlags
= inevt
.mask
;
258 int flags
= Native2WatcherFlags(nativeFlags
);
260 // check out for error/warning condition
261 if (flags
& wxFSW_EVENT_WARNING
|| flags
& wxFSW_EVENT_ERROR
)
263 wxString errMsg
= GetErrorDescription(Watcher2NativeFlags(flags
));
264 wxFileSystemWatcherEvent
event(flags
, errMsg
);
267 // filter out ignored events and those not asked for.
268 // we never filter out warnings or exceptions
269 else if ((flags
== 0) || !(flags
& watch
.GetFlags()))
274 else if (nativeFlags
& IN_MOVE
)
276 wxInotifyCookies::iterator it
= m_cookies
.find(inevt
.cookie
);
277 if ( it
== m_cookies
.end() )
279 int size
= sizeof(inevt
) + inevt
.len
;
280 inotify_event
* e
= (inotify_event
*) operator new (size
);
281 memcpy(e
, &inevt
, size
);
283 wxInotifyCookies::value_type
val(e
->cookie
, e
);
284 m_cookies
.insert(val
);
288 inotify_event
& oldinevt
= *(it
->second
);
290 wxFileSystemWatcherEvent
event(flags
);
291 if ( inevt
.mask
& IN_MOVED_FROM
)
293 event
.SetPath(GetEventPath(watch
, inevt
));
294 event
.SetNewPath(GetEventPath(watch
, oldinevt
));
298 event
.SetPath(GetEventPath(watch
, oldinevt
));
299 event
.SetNewPath(GetEventPath(watch
, inevt
));
307 // every other kind of event
310 wxFileName path
= GetEventPath(watch
, inevt
);
311 wxFileSystemWatcherEvent
event(flags
, path
, path
);
316 void ProcessRenames()
318 wxInotifyCookies::iterator it
= m_cookies
.begin();
319 while ( it
!= m_cookies
.end() )
321 inotify_event
& inevt
= *(it
->second
);
323 wxLogTrace(wxTRACE_FSWATCHER
, "Processing pending rename events");
324 wxLogTrace(wxTRACE_FSWATCHER
, InotifyEventToString(inevt
));
326 // get watch entry for this event
327 wxFSWatchEntryDescriptors::iterator wit
= m_watchMap
.find(inevt
.wd
);
328 wxCHECK_RET(wit
!= m_watchMap
.end(),
329 "Watch descriptor not present in the watch map!");
331 wxFSWatchEntry
& watch
= *(wit
->second
);
332 int flags
= Native2WatcherFlags(inevt
.mask
);
333 wxFileName path
= GetEventPath(watch
, inevt
);
334 wxFileSystemWatcherEvent
event(flags
, path
, path
);
339 it
= m_cookies
.begin();
343 void SendEvent(wxFileSystemWatcherEvent
& evt
)
345 wxLogTrace(wxTRACE_FSWATCHER
, evt
.ToString());
346 m_watcher
->GetOwner()->ProcessEvent(evt
);
349 int ReadEventsToBuf(char* buf
, int size
)
351 wxCHECK_MSG( IsOk(), false,
352 "Inotify not initialized or invalid inotify descriptor" );
354 memset(buf
, 0, size
);
355 ssize_t left
= read(m_source
->GetResource(), buf
, size
);
358 wxLogSysError(_("Unable to read from inotify descriptor"));
363 wxLogWarning(_("EOF while reading from inotify descriptor"));
370 static wxString
InotifyEventToString(const inotify_event
& inevt
)
372 wxString mask
= (inevt
.mask
& IN_ISDIR
) ?
373 wxString::Format("IS_DIR | %u", inevt
.mask
& ~IN_ISDIR
) :
374 wxString::Format("%u", inevt
.mask
);
375 return wxString::Format("Event: wd=%d, mask=%s, cookie=%u, len=%u, "
376 "name=%s", inevt
.wd
, mask
, inevt
.cookie
,
377 inevt
.len
, inevt
.name
);
380 static wxFileName
GetEventPath(const wxFSWatchEntry
& watch
,
381 const inotify_event
& inevt
)
383 // only when dir is watched, we have non-empty e.name
384 wxFileName path
= watch
.GetPath();
387 path
= wxFileName(path
.GetPath(), inevt
.name
);
392 static int Watcher2NativeFlags(int WXUNUSED(flags
))
394 // TODO: it would be nice to subscribe only to the events we really need
395 return IN_ALL_EVENTS
;
398 static int Native2WatcherFlags(int flags
)
400 static const int flag_mapping
[][2] = {
401 { IN_ACCESS
, wxFSW_EVENT_ACCESS
}, // generated during read!
402 { IN_MODIFY
, wxFSW_EVENT_MODIFY
},
404 { IN_CLOSE_WRITE
, 0 },
405 { IN_CLOSE_NOWRITE
, 0 },
407 { IN_MOVED_FROM
, wxFSW_EVENT_RENAME
},
408 { IN_MOVED_TO
, wxFSW_EVENT_RENAME
},
409 { IN_CREATE
, wxFSW_EVENT_CREATE
},
410 { IN_DELETE
, wxFSW_EVENT_DELETE
},
411 { IN_DELETE_SELF
, wxFSW_EVENT_DELETE
},
412 { IN_MOVE_SELF
, wxFSW_EVENT_DELETE
},
414 { IN_UNMOUNT
, wxFSW_EVENT_ERROR
},
415 { IN_Q_OVERFLOW
, wxFSW_EVENT_WARNING
},
417 // ignored, because this is genereted mainly by watcher::Remove()
422 for ( ; i
< WXSIZEOF(flag_mapping
); ++i
) {
423 // in this mapping multiple flags at once don't happen
424 if (flags
& flag_mapping
[i
][0])
425 return flag_mapping
[i
][1];
429 wxFAIL_MSG(wxString::Format("Unknown inotify event mask %u", flags
));
434 * Returns error description for specified inotify mask
436 static const wxString
GetErrorDescription(int flag
)
441 return _("File system containing watched object was unmounted");
443 return _("Event queue overflowed");
447 wxFAIL_MSG(wxString::Format("Unknown inotify event mask %u", flag
));
448 return wxEmptyString
;
451 wxFSWSourceHandler
* m_handler
; // handler for inotify event source
452 wxFSWatchEntryDescriptors m_watchMap
; // inotify wd=>wxFSWatchEntry* map
453 wxInotifyCookies m_cookies
; // map to track renames
454 wxEventLoopBase
* m_loop
;
455 wxUnixEventLoopSource
* m_source
; // our event loop source
459 // ============================================================================
460 // wxFSWSourceHandler implementation
461 // ============================================================================
463 // once we get signaled to read, actuall event reading occurs
464 void wxFSWSourceHandler::OnReadWaiting()
466 wxLogTrace(wxTRACE_FSWATCHER
, "--- OnReadWaiting ---");
467 m_service
->ReadEvents();
470 void wxFSWSourceHandler::OnWriteWaiting()
472 wxFAIL_MSG("We never write to inotify descriptor.");
475 void wxFSWSourceHandler::OnExceptionWaiting()
477 wxFAIL_MSG("We never receive exceptions on inotify descriptor.");
481 // ============================================================================
482 // wxInotifyFileSystemWatcher implementation
483 // ============================================================================
485 wxInotifyFileSystemWatcher::wxInotifyFileSystemWatcher()
486 : wxFileSystemWatcherBase()
491 wxInotifyFileSystemWatcher::wxInotifyFileSystemWatcher(const wxFileName
& path
,
493 : wxFileSystemWatcherBase()
505 wxInotifyFileSystemWatcher::~wxInotifyFileSystemWatcher()
509 bool wxInotifyFileSystemWatcher::Init()
511 m_service
= new wxFSWatcherImplUnix(this);
512 return m_service
->Init();
515 #endif // wxHAS_INOTIFY
517 #endif // wxUSE_FSWATCHER