229af98eee66685b107d8d8ed9f2b407ce8350c2
[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_loop(NULL),
49 m_source(NULL)
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 wxCHECK_MSG( m_loop == NULL, false, "Event loop != NULL");
69
70 m_loop = (wxEventLoopBase::GetActive());
71 wxCHECK_MSG( m_loop, false, "File system watcher needs an active loop" );
72
73 int fd = inotify_init();
74 if (fd == -1)
75 {
76 wxLogSysError( _("Unable to create inotify instance") );
77 return false;
78 }
79
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();
84 }
85
86 bool Close()
87 {
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" );
92
93 // ignore errors
94 (void) UnregisterSource();
95
96 int ret = close(m_source->GetResource());
97 if (ret == -1)
98 {
99 wxLogSysError( _("Unable to close inotify instance") );
100 }
101 m_source->Invalidate();
102
103 return ret != -1;
104 }
105
106 virtual bool DoAdd(wxSharedPtr<wxFSWatchEntryUnix> watch)
107 {
108 wxCHECK_MSG( IsOk(), false,
109 "Inotify not initialized or invalid inotify descriptor" );
110
111 int wd = DoAddInotify(watch.get());
112 if (wd == -1)
113 {
114 wxLogSysError( _("Unable to add inotify watch") );
115 return false;
116 }
117
118 wxFSWatchEntryDescriptors::value_type val(wd, watch.get());
119 if (!m_watchMap.insert(val).second)
120 {
121 wxFAIL_MSG( wxString::Format( "Path %s is already watched",
122 watch->GetPath()) );
123 return false;
124 }
125
126 return true;
127 }
128
129 virtual bool DoRemove(wxSharedPtr<wxFSWatchEntryUnix> watch)
130 {
131 wxCHECK_MSG( IsOk(), false,
132 "Inotify not initialized or invalid inotify descriptor" );
133
134 int ret = DoRemoveInotify(watch.get());
135 if (ret == -1)
136 {
137 wxLogSysError( _("Unable to remove inotify watch") );
138 return false;
139 }
140
141 if (m_watchMap.erase(watch->GetWatchDescriptor()) != 1)
142 {
143 wxFAIL_MSG( wxString::Format("Path %s is not watched",
144 watch->GetPath()) );
145 }
146 watch->SetWatchDescriptor(-1);
147 return true;
148 }
149
150 virtual bool RemoveAll()
151 {
152 wxFSWatchEntries::iterator it = m_watches.begin();
153 for ( ; it != m_watches.end(); ++it )
154 {
155 (void) DoRemove(it->second);
156 }
157 m_watches.clear();
158 return true;
159 }
160
161 int ReadEvents()
162 {
163 wxCHECK_MSG( IsOk(), -1,
164 "Inotify not initialized or invalid inotify descriptor" );
165
166 // read events
167 // TODO differentiate depending on params
168 char buf[128 * sizeof(inotify_event)];
169 int left = ReadEventsToBuf(buf, sizeof(buf));
170 if (left == -1)
171 return -1;
172
173 // left > 0, we have events
174 char* memory = buf;
175 int event_count = 0;
176 while (left > 0) // OPT checking 'memory' would suffice
177 {
178 event_count++;
179 inotify_event* e = (inotify_event*)memory;
180
181 // process one inotify_event
182 ProcessNativeEvent(*e);
183
184 int offset = sizeof(inotify_event) + e->len;
185 left -= offset;
186 memory += offset;
187 }
188
189 // take care of unmatched renames
190 ProcessRenames();
191
192 wxLogTrace(wxTRACE_FSWATCHER, "We had %d native events", event_count);
193 return event_count;
194 }
195
196 bool IsOk()
197 {
198 return m_source && m_source->IsOk();
199 }
200
201 protected:
202 bool RegisterSource()
203 {
204 wxCHECK_MSG( IsOk(), false,
205 "Inotify not initialized or invalid inotify descriptor" );
206
207 bool ret = m_loop->AddSource(m_source);
208 return ret;
209 }
210
211 bool UnregisterSource()
212 {
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" );
217
218 bool ret = m_loop->RemoveSource(m_source);
219 m_loop = NULL;
220 return ret;
221 }
222
223 int DoAddInotify(wxFSWatchEntry* watch)
224 {
225 int flags = Watcher2NativeFlags(watch->GetFlags());
226 int wd = inotify_add_watch(m_source->GetResource(),
227 watch->GetPath().fn_str(),
228 flags);
229 // finally we can set watch descriptor
230 watch->SetWatchDescriptor(wd);
231 return wd;
232 }
233
234 int DoRemoveInotify(wxFSWatchEntry* watch)
235 {
236 return inotify_rm_watch(m_source->GetResource(),
237 watch->GetWatchDescriptor());
238 }
239
240 void ProcessNativeEvent(const inotify_event& inevt)
241 {
242 wxLogTrace(wxTRACE_FSWATCHER, InotifyEventToString(inevt));
243
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)
247 {
248 return;
249 }
250
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!");
255
256 wxFSWatchEntry& watch = *(it->second);
257 int nativeFlags = inevt.mask;
258 int flags = Native2WatcherFlags(nativeFlags);
259
260 // check out for error/warning condition
261 if (flags & wxFSW_EVENT_WARNING || flags & wxFSW_EVENT_ERROR)
262 {
263 wxString errMsg = GetErrorDescription(Watcher2NativeFlags(flags));
264 wxFileSystemWatcherEvent event(flags, errMsg);
265 SendEvent(event);
266 }
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()))
270 {
271 return;
272 }
273 // renames
274 else if (nativeFlags & IN_MOVE)
275 {
276 wxInotifyCookies::iterator it = m_cookies.find(inevt.cookie);
277 if ( it == m_cookies.end() )
278 {
279 int size = sizeof(inevt) + inevt.len;
280 inotify_event* e = (inotify_event*) operator new (size);
281 memcpy(e, &inevt, size);
282
283 wxInotifyCookies::value_type val(e->cookie, e);
284 m_cookies.insert(val);
285 }
286 else
287 {
288 inotify_event& oldinevt = *(it->second);
289
290 wxFileSystemWatcherEvent event(flags);
291 if ( inevt.mask & IN_MOVED_FROM )
292 {
293 event.SetPath(GetEventPath(watch, inevt));
294 event.SetNewPath(GetEventPath(watch, oldinevt));
295 }
296 else
297 {
298 event.SetPath(GetEventPath(watch, oldinevt));
299 event.SetNewPath(GetEventPath(watch, inevt));
300 }
301 SendEvent(event);
302
303 m_cookies.erase(it);
304 delete &oldinevt;
305 }
306 }
307 // every other kind of event
308 else
309 {
310 wxFileName path = GetEventPath(watch, inevt);
311 wxFileSystemWatcherEvent event(flags, path, path);
312 SendEvent(event);
313 }
314 }
315
316 void ProcessRenames()
317 {
318 wxInotifyCookies::iterator it = m_cookies.begin();
319 while ( it != m_cookies.end() )
320 {
321 inotify_event& inevt = *(it->second);
322
323 wxLogTrace(wxTRACE_FSWATCHER, "Processing pending rename events");
324 wxLogTrace(wxTRACE_FSWATCHER, InotifyEventToString(inevt));
325
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!");
330
331 wxFSWatchEntry& watch = *(wit->second);
332 int flags = Native2WatcherFlags(inevt.mask);
333 wxFileName path = GetEventPath(watch, inevt);
334 wxFileSystemWatcherEvent event(flags, path, path);
335 SendEvent(event);
336
337 m_cookies.erase(it);
338 delete &inevt;
339 it = m_cookies.begin();
340 }
341 }
342
343 void SendEvent(wxFileSystemWatcherEvent& evt)
344 {
345 wxLogTrace(wxTRACE_FSWATCHER, evt.ToString());
346 m_watcher->GetOwner()->ProcessEvent(evt);
347 }
348
349 int ReadEventsToBuf(char* buf, int size)
350 {
351 wxCHECK_MSG( IsOk(), false,
352 "Inotify not initialized or invalid inotify descriptor" );
353
354 memset(buf, 0, size);
355 ssize_t left = read(m_source->GetResource(), buf, size);
356 if (left == -1)
357 {
358 wxLogSysError(_("Unable to read from inotify descriptor"));
359 return -1;
360 }
361 else if (left == 0)
362 {
363 wxLogWarning(_("EOF while reading from inotify descriptor"));
364 return -1;
365 }
366
367 return left;
368 }
369
370 static wxString InotifyEventToString(const inotify_event& inevt)
371 {
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);
378 }
379
380 static wxFileName GetEventPath(const wxFSWatchEntry& watch,
381 const inotify_event& inevt)
382 {
383 // only when dir is watched, we have non-empty e.name
384 wxFileName path = watch.GetPath();
385 if (path.IsDir())
386 {
387 path = wxFileName(path.GetPath(), inevt.name);
388 }
389 return path;
390 }
391
392 static int Watcher2NativeFlags(int WXUNUSED(flags))
393 {
394 // TODO: it would be nice to subscribe only to the events we really need
395 return IN_ALL_EVENTS;
396 }
397
398 static int Native2WatcherFlags(int flags)
399 {
400 static const int flag_mapping[][2] = {
401 { IN_ACCESS, wxFSW_EVENT_ACCESS }, // generated during read!
402 { IN_MODIFY, wxFSW_EVENT_MODIFY },
403 { IN_ATTRIB, 0 },
404 { IN_CLOSE_WRITE, 0 },
405 { IN_CLOSE_NOWRITE, 0 },
406 { IN_OPEN, 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 },
413
414 { IN_UNMOUNT, wxFSW_EVENT_ERROR },
415 { IN_Q_OVERFLOW, wxFSW_EVENT_WARNING},
416
417 // ignored, because this is genereted mainly by watcher::Remove()
418 { IN_IGNORED, 0 }
419 };
420
421 unsigned int i=0;
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];
426 }
427
428 // never reached
429 wxFAIL_MSG(wxString::Format("Unknown inotify event mask %u", flags));
430 return -1;
431 }
432
433 /**
434 * Returns error description for specified inotify mask
435 */
436 static const wxString GetErrorDescription(int flag)
437 {
438 switch ( flag )
439 {
440 case IN_UNMOUNT:
441 return _("File system containing watched object was unmounted");
442 case IN_Q_OVERFLOW:
443 return _("Event queue overflowed");
444 }
445
446 // never reached
447 wxFAIL_MSG(wxString::Format("Unknown inotify event mask %u", flag));
448 return wxEmptyString;
449 }
450
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
456 };
457
458
459 // ============================================================================
460 // wxFSWSourceHandler implementation
461 // ============================================================================
462
463 // once we get signaled to read, actuall event reading occurs
464 void wxFSWSourceHandler::OnReadWaiting()
465 {
466 wxLogTrace(wxTRACE_FSWATCHER, "--- OnReadWaiting ---");
467 m_service->ReadEvents();
468 }
469
470 void wxFSWSourceHandler::OnWriteWaiting()
471 {
472 wxFAIL_MSG("We never write to inotify descriptor.");
473 }
474
475 void wxFSWSourceHandler::OnExceptionWaiting()
476 {
477 wxFAIL_MSG("We never receive exceptions on inotify descriptor.");
478 }
479
480
481 // ============================================================================
482 // wxInotifyFileSystemWatcher implementation
483 // ============================================================================
484
485 wxInotifyFileSystemWatcher::wxInotifyFileSystemWatcher()
486 : wxFileSystemWatcherBase()
487 {
488 Init();
489 }
490
491 wxInotifyFileSystemWatcher::wxInotifyFileSystemWatcher(const wxFileName& path,
492 int events)
493 : wxFileSystemWatcherBase()
494 {
495 if (!Init())
496 {
497 if (m_service)
498 delete m_service;
499 return;
500 }
501
502 Add(path, events);
503 }
504
505 wxInotifyFileSystemWatcher::~wxInotifyFileSystemWatcher()
506 {
507 }
508
509 bool wxInotifyFileSystemWatcher::Init()
510 {
511 m_service = new wxFSWatcherImplUnix(this);
512 return m_service->Init();
513 }
514
515 #endif // wxHAS_INOTIFY
516
517 #endif // wxUSE_FSWATCHER