aboutsummaryrefslogtreecommitdiff
path: root/src/3rdParty/efsw/FileWatcherInotify.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/3rdParty/efsw/FileWatcherInotify.cpp')
-rwxr-xr-xsrc/3rdParty/efsw/FileWatcherInotify.cpp599
1 files changed, 599 insertions, 0 deletions
diff --git a/src/3rdParty/efsw/FileWatcherInotify.cpp b/src/3rdParty/efsw/FileWatcherInotify.cpp
new file mode 100755
index 0000000..e0da76b
--- /dev/null
+++ b/src/3rdParty/efsw/FileWatcherInotify.cpp
@@ -0,0 +1,599 @@
1#include <efsw/FileWatcherInotify.hpp>
2
3#if EFSW_PLATFORM == EFSW_PLATFORM_INOTIFY
4
5#include <errno.h>
6#include <fcntl.h>
7#include <stdio.h>
8#include <string.h>
9#include <sys/stat.h>
10#include <unistd.h>
11
12#ifdef EFSW_INOTIFY_NOSYS
13#include <efsw/inotify-nosys.h>
14#else
15#include <sys/inotify.h>
16#endif
17
18#include <efsw/Debug.hpp>
19#include <efsw/FileSystem.hpp>
20#include <efsw/Lock.hpp>
21#include <efsw/String.hpp>
22#include <efsw/System.hpp>
23
24#define BUFF_SIZE ( ( sizeof( struct inotify_event ) + FILENAME_MAX ) * 1024 )
25
26namespace efsw {
27
28FileWatcherInotify::FileWatcherInotify( FileWatcher* parent ) :
29 FileWatcherImpl( parent ), mFD( -1 ), mThread( NULL ) {
30 mFD = inotify_init();
31
32 if ( mFD < 0 ) {
33 efDEBUG( "Error: %s\n", strerror( errno ) );
34 } else {
35 mInitOK = true;
36 }
37}
38
39FileWatcherInotify::~FileWatcherInotify() {
40 mInitOK = false;
41
42 Lock initLock( mInitLock );
43
44 efSAFE_DELETE( mThread );
45
46 Lock l( mWatchesLock );
47 Lock l2( mRealWatchesLock );
48 WatchMap::iterator iter = mWatches.begin();
49 WatchMap::iterator end = mWatches.end();
50
51 for ( ; iter != end; ++iter ) {
52 efSAFE_DELETE( iter->second );
53 }
54
55 mWatches.clear();
56
57 if ( mFD != -1 ) {
58 close( mFD );
59 mFD = -1;
60 }
61}
62
63WatchID FileWatcherInotify::addWatch( const std::string& directory, FileWatchListener* watcher,
64 bool recursive ) {
65 if ( !mInitOK )
66 return Errors::Log::createLastError( Errors::Unspecified, directory );
67 Lock initLock( mInitLock );
68 return addWatch( directory, watcher, recursive, NULL );
69}
70
71WatchID FileWatcherInotify::addWatch( const std::string& directory, FileWatchListener* watcher,
72 bool recursive, WatcherInotify* parent ) {
73 std::string dir( directory );
74
75 FileSystem::dirAddSlashAtEnd( dir );
76
77 FileInfo fi( dir );
78
79 if ( !fi.isDirectory() ) {
80 return Errors::Log::createLastError( Errors::FileNotFound, dir );
81 } else if ( !fi.isReadable() ) {
82 return Errors::Log::createLastError( Errors::FileNotReadable, dir );
83 } else if ( pathInWatches( dir ) ) {
84 return Errors::Log::createLastError( Errors::FileRepeated, directory );
85 } else if ( NULL != parent && FileSystem::isRemoteFS( dir ) ) {
86 return Errors::Log::createLastError( Errors::FileRemote, dir );
87 }
88
89 /// Check if the directory is a symbolic link
90 std::string curPath;
91 std::string link( FileSystem::getLinkRealPath( dir, curPath ) );
92
93 if ( "" != link ) {
94 /// Avoid adding symlinks directories if it's now enabled
95 if ( NULL != parent && !mFileWatcher->followSymlinks() ) {
96 return Errors::Log::createLastError( Errors::FileOutOfScope, dir );
97 }
98
99 /// If it's a symlink check if the realpath exists as a watcher, or
100 /// if the path is outside the current dir
101 if ( pathInWatches( link ) ) {
102 return Errors::Log::createLastError( Errors::FileRepeated, directory );
103 } else if ( !linkAllowed( curPath, link ) ) {
104 return Errors::Log::createLastError( Errors::FileOutOfScope, dir );
105 } else {
106 dir = link;
107 }
108 }
109
110 int wd = inotify_add_watch( mFD, dir.c_str(),
111 IN_CLOSE_WRITE | IN_MOVED_TO | IN_CREATE | IN_MOVED_FROM |
112 IN_DELETE | IN_MODIFY );
113
114 if ( wd < 0 ) {
115 if ( errno == ENOENT ) {
116 return Errors::Log::createLastError( Errors::FileNotFound, dir );
117 } else {
118 return Errors::Log::createLastError( Errors::Unspecified,
119 std::string( strerror( errno ) ) );
120 }
121 }
122
123 efDEBUG( "Added watch %s with id: %d\n", dir.c_str(), wd );
124
125 WatcherInotify* pWatch = new WatcherInotify();
126 pWatch->Listener = watcher;
127 pWatch->ID = parent ? parent->ID : wd;
128 pWatch->InotifyID = wd;
129 pWatch->Directory = dir;
130 pWatch->Recursive = recursive;
131 pWatch->Parent = parent;
132
133 {
134 Lock lock( mWatchesLock );
135 mWatches.insert( std::make_pair( wd, pWatch ) );
136 }
137
138 if ( NULL == pWatch->Parent ) {
139 Lock l( mRealWatchesLock );
140 mRealWatches[pWatch->InotifyID] = pWatch;
141 }
142
143 if ( pWatch->Recursive ) {
144 std::map<std::string, FileInfo> files = FileSystem::filesInfoFromPath( pWatch->Directory );
145 std::map<std::string, FileInfo>::iterator it = files.begin();
146
147 for ( ; it != files.end(); ++it ) {
148 if ( !mInitOK )
149 break;
150
151 const FileInfo& cfi = it->second;
152
153 if ( cfi.isDirectory() && cfi.isReadable() ) {
154 addWatch( cfi.Filepath, watcher, recursive, pWatch );
155 }
156 }
157 }
158
159 return wd;
160}
161
162void FileWatcherInotify::removeWatchLocked( WatchID watchid ) {
163 WatchMap::iterator iter = mWatches.find( watchid );
164
165 WatcherInotify* watch = iter->second;
166
167 for ( std::vector<std::pair<WatcherInotify*, std::string>>::iterator itm =
168 mMovedOutsideWatches.begin();
169 mMovedOutsideWatches.end() != itm; ++itm ) {
170 if ( itm->first == watch ) {
171 mMovedOutsideWatches.erase( itm );
172 break;
173 }
174 }
175
176 if ( watch->Recursive ) {
177 WatchMap::iterator it = mWatches.begin();
178 std::list<WatchID> eraseWatches;
179
180 for ( ; it != mWatches.end(); ++it ) {
181 if ( it->second != watch && it->second->inParentTree( watch ) ) {
182 eraseWatches.push_back( it->second->InotifyID );
183 }
184 }
185
186 for ( std::list<WatchID>::iterator eit = eraseWatches.begin(); eit != eraseWatches.end();
187 ++eit ) {
188 removeWatch( *eit );
189 }
190 }
191
192 mWatches.erase( iter );
193
194 if ( NULL == watch->Parent ) {
195 WatchMap::iterator eraseit = mRealWatches.find( watch->InotifyID );
196
197 if ( eraseit != mRealWatches.end() ) {
198 mRealWatches.erase( eraseit );
199 }
200 }
201
202 int err = inotify_rm_watch( mFD, watchid );
203
204 if ( err < 0 ) {
205 efDEBUG( "Error removing watch %d: %s\n", watchid, strerror( errno ) );
206 } else {
207 efDEBUG( "Removed watch %s with id: %d\n", watch->Directory.c_str(), watchid );
208 }
209
210 efSAFE_DELETE( watch );
211}
212
213void FileWatcherInotify::removeWatch( const std::string& directory ) {
214 if ( !mInitOK )
215 return;
216 Lock initLock( mInitLock );
217 Lock lock( mWatchesLock );
218 Lock l( mRealWatchesLock );
219
220 WatchMap::iterator iter = mWatches.begin();
221
222 for ( ; iter != mWatches.end(); ++iter ) {
223 if ( directory == iter->second->Directory ) {
224 WatcherInotify* watch = iter->second;
225
226 if ( watch->Recursive ) {
227 WatchMap::iterator it = mWatches.begin();
228 std::list<WatchID> eraseWatches;
229
230 for ( ; it != mWatches.end(); ++it ) {
231 if ( it->second->inParentTree( watch ) ) {
232 eraseWatches.push_back( it->second->InotifyID );
233 }
234 }
235
236 for ( std::list<WatchID>::iterator eit = eraseWatches.begin();
237 eit != eraseWatches.end(); ++eit ) {
238 removeWatchLocked( *eit );
239 }
240 }
241
242 mWatches.erase( iter );
243
244 if ( NULL == watch->Parent ) {
245 WatchMap::iterator eraseit = mRealWatches.find( watch->InotifyID );
246
247 if ( eraseit != mRealWatches.end() ) {
248 mRealWatches.erase( eraseit );
249 }
250 }
251
252 int err = inotify_rm_watch( mFD, watch->InotifyID );
253
254 if ( err < 0 ) {
255 efDEBUG( "Error removing watch %d: %s\n", watch->InotifyID, strerror( errno ) );
256 } else {
257 efDEBUG( "Removed watch %s with id: %d\n", watch->Directory.c_str(),
258 watch->InotifyID );
259 }
260
261 efSAFE_DELETE( watch );
262
263 break;
264 }
265 }
266}
267
268void FileWatcherInotify::removeWatch( WatchID watchid ) {
269 if ( !mInitOK )
270 return;
271 Lock initLock( mInitLock );
272 Lock lock( mWatchesLock );
273
274 WatchMap::iterator iter = mWatches.find( watchid );
275
276 if ( iter == mWatches.end() ) {
277 return;
278 }
279
280 removeWatchLocked( watchid );
281}
282
283void FileWatcherInotify::watch() {
284 if ( NULL == mThread ) {
285 mThread = new Thread( &FileWatcherInotify::run, this );
286 mThread->launch();
287 }
288}
289
290Watcher* FileWatcherInotify::watcherContainsDirectory( std::string dir ) {
291 FileSystem::dirRemoveSlashAtEnd( dir );
292 std::string watcherPath = FileSystem::pathRemoveFileName( dir );
293 FileSystem::dirAddSlashAtEnd( watcherPath );
294 Lock lock( mWatchesLock );
295
296 for ( WatchMap::iterator it = mWatches.begin(); it != mWatches.end(); ++it ) {
297 Watcher* watcher = it->second;
298
299 if ( watcher->Directory == watcherPath ) {
300 return watcher;
301 }
302 }
303
304 return NULL;
305}
306
307void FileWatcherInotify::run() {
308 char* buff = new char[BUFF_SIZE];
309 memset( buff, 0, BUFF_SIZE );
310 WatchMap::iterator wit;
311
312 WatcherInotify* currentMoveFrom = NULL;
313 u_int32_t currentMoveCookie = -1;
314 bool lastWasMovedFrom = false;
315 std::string prevOldFileName;
316
317 do {
318 fd_set rfds;
319 FD_ZERO( &rfds );
320 FD_SET( mFD, &rfds );
321 timeval timeout;
322 timeout.tv_sec = 0;
323 timeout.tv_usec = 100000;
324
325 if ( select( FD_SETSIZE, &rfds, NULL, NULL, &timeout ) > 0 ) {
326 ssize_t len;
327
328 len = read( mFD, buff, BUFF_SIZE );
329
330 if ( len != -1 ) {
331 ssize_t i = 0;
332
333 while ( i < len ) {
334 struct inotify_event* pevent = (struct inotify_event*)&buff[i];
335
336 {
337 {
338 Lock lock( mWatchesLock );
339
340 wit = mWatches.find( pevent->wd );
341 }
342
343 if ( wit != mWatches.end() ) {
344 handleAction( wit->second, (char*)pevent->name, pevent->mask );
345
346 if ( ( pevent->mask & IN_MOVED_TO ) && wit->second == currentMoveFrom &&
347 pevent->cookie == currentMoveCookie ) {
348 /// make pair success
349 currentMoveFrom = NULL;
350 currentMoveCookie = -1;
351 } else if ( pevent->mask & IN_MOVED_FROM ) {
352 // Previous event was moved from and current event is moved from
353 // Treat it as a DELETE or moved ouside watches
354 if ( lastWasMovedFrom && currentMoveFrom ) {
355 mMovedOutsideWatches.push_back(
356 std::make_pair( currentMoveFrom, prevOldFileName ) );
357 }
358
359 currentMoveFrom = wit->second;
360 currentMoveCookie = pevent->cookie;
361 } else {
362 /// Keep track of the IN_MOVED_FROM events to know
363 /// if the IN_MOVED_TO event is also fired
364 if ( currentMoveFrom ) {
365 mMovedOutsideWatches.push_back(
366 std::make_pair( currentMoveFrom, prevOldFileName ) );
367 }
368
369 currentMoveFrom = NULL;
370 currentMoveCookie = -1;
371 }
372 }
373
374 lastWasMovedFrom = ( pevent->mask & IN_MOVED_FROM ) != 0;
375 if ( pevent->mask & IN_MOVED_FROM )
376 prevOldFileName = std::string( (char*)pevent->name );
377 }
378
379 i += sizeof( struct inotify_event ) + pevent->len;
380 }
381 }
382 } else {
383 // Here means no event received
384 // If last event is IN_MOVED_FROM, we assume no IN_MOVED_TO
385 if ( currentMoveFrom ) {
386 mMovedOutsideWatches.push_back(
387 std::make_pair( currentMoveFrom, currentMoveFrom->OldFileName ) );
388 }
389
390 currentMoveFrom = NULL;
391 currentMoveCookie = -1;
392 }
393
394 if ( !mMovedOutsideWatches.empty() ) {
395 // We need to make a copy since the element mMovedOutsideWatches could be modified
396 // during the iteration.
397 std::vector<std::pair<WatcherInotify*, std::string>> movedOutsideWatches(
398 mMovedOutsideWatches );
399
400 /// In case that the IN_MOVED_TO is never fired means that the file was moved to other
401 /// folder
402 for ( std::vector<std::pair<WatcherInotify*, std::string>>::iterator it =
403 movedOutsideWatches.begin();
404 it != movedOutsideWatches.end(); ++it ) {
405
406 // Skip if the watch has already being removed
407 if ( mMovedOutsideWatches.size() != movedOutsideWatches.size() ) {
408 bool found = false;
409 for ( std::vector<std::pair<WatcherInotify*, std::string>>::iterator itm =
410 mMovedOutsideWatches.begin();
411 mMovedOutsideWatches.end() != itm; ++itm ) {
412 if ( itm->first == it->first ) {
413 found = true;
414 break;
415 }
416 }
417 if ( !found )
418 continue;
419 }
420
421 Watcher* watch = ( *it ).first;
422 const std::string& oldFileName = ( *it ).second;
423
424 /// Check if the file move was a folder already being watched
425 std::list<Watcher*> eraseWatches;
426
427 {
428 Lock lock( mWatchesLock );
429
430 for ( ; wit != mWatches.end(); ++wit ) {
431 Watcher* oldWatch = wit->second;
432
433 if ( oldWatch != watch &&
434 -1 != String::strStartsWith( watch->Directory + oldFileName + "/",
435 oldWatch->Directory ) ) {
436 eraseWatches.push_back( oldWatch );
437 }
438 }
439 }
440
441 /// Remove invalid watches
442 eraseWatches.sort();
443
444 if ( eraseWatches.empty() ) {
445 handleAction( watch, oldFileName, IN_DELETE );
446 } else {
447 for ( std::list<Watcher*>::reverse_iterator eit = eraseWatches.rbegin();
448 eit != eraseWatches.rend(); ++eit ) {
449 Watcher* rmWatch = *eit;
450
451 /// Create Delete event for removed watches that have been moved too
452 if ( Watcher* cntWatch = watcherContainsDirectory( rmWatch->Directory ) ) {
453 handleAction( cntWatch,
454 FileSystem::fileNameFromPath( rmWatch->Directory ),
455 IN_DELETE );
456 }
457 }
458 }
459 }
460
461 mMovedOutsideWatches.clear();
462 }
463 } while ( mInitOK );
464
465 delete[] buff;
466}
467
468void FileWatcherInotify::checkForNewWatcher( Watcher* watch, std::string fpath ) {
469 FileSystem::dirAddSlashAtEnd( fpath );
470
471 /// If the watcher is recursive, checks if the new file is a folder, and creates a watcher
472 if ( watch->Recursive && FileSystem::isDirectory( fpath ) ) {
473 bool found = false;
474
475 {
476 Lock lock( mWatchesLock );
477
478 /// First check if exists
479 for ( WatchMap::iterator it = mWatches.begin(); it != mWatches.end(); ++it ) {
480 if ( it->second->Directory == fpath ) {
481 found = true;
482 break;
483 }
484 }
485 }
486
487 if ( !found ) {
488 addWatch( fpath, watch->Listener, watch->Recursive,
489 static_cast<WatcherInotify*>( watch ) );
490 }
491 }
492}
493
494void FileWatcherInotify::handleAction( Watcher* watch, const std::string& filename,
495 unsigned long action, std::string ) {
496 if ( !watch || !watch->Listener || !mInitOK ) {
497 return;
498 }
499
500 Lock initLock( mInitLock );
501
502 std::string fpath( watch->Directory + filename );
503
504 if ( ( IN_CLOSE_WRITE & action ) || ( IN_MODIFY & action ) ) {
505 watch->Listener->handleFileAction( watch->ID, watch->Directory, filename,
506 Actions::Modified );
507 } else if ( IN_MOVED_TO & action ) {
508 /// If OldFileName doesn't exist means that the file has been moved from other folder, so we
509 /// just send the Add event
510 if ( watch->OldFileName.empty() ) {
511 watch->Listener->handleFileAction( watch->ID, watch->Directory, filename,
512 Actions::Add );
513
514 watch->Listener->handleFileAction( watch->ID, watch->Directory, filename,
515 Actions::Modified );
516
517 checkForNewWatcher( watch, fpath );
518 } else {
519 watch->Listener->handleFileAction( watch->ID, watch->Directory, filename,
520 Actions::Moved, watch->OldFileName );
521 }
522
523 if ( watch->Recursive && FileSystem::isDirectory( fpath ) ) {
524 /// Update the new directory path
525 std::string opath( watch->Directory + watch->OldFileName );
526 FileSystem::dirAddSlashAtEnd( opath );
527 FileSystem::dirAddSlashAtEnd( fpath );
528
529 Lock lock( mWatchesLock );
530
531 for ( WatchMap::iterator it = mWatches.begin(); it != mWatches.end(); ++it ) {
532 if ( it->second->Directory == opath ) {
533 it->second->Directory = fpath;
534 it->second->DirInfo = FileInfo( fpath );
535 } else if ( -1 != String::strStartsWith( opath, it->second->Directory ) ) {
536 it->second->Directory = fpath + it->second->Directory.substr( opath.size() );
537 it->second->DirInfo.Filepath = it->second->Directory;
538 }
539 }
540 }
541
542 watch->OldFileName = "";
543 } else if ( IN_CREATE & action ) {
544 watch->Listener->handleFileAction( watch->ID, watch->Directory, filename, Actions::Add );
545
546 checkForNewWatcher( watch, fpath );
547 } else if ( IN_MOVED_FROM & action ) {
548 watch->OldFileName = filename;
549 } else if ( IN_DELETE & action ) {
550 watch->Listener->handleFileAction( watch->ID, watch->Directory, filename, Actions::Delete );
551
552 FileSystem::dirAddSlashAtEnd( fpath );
553
554 /// If the file erased is a directory and recursive is enabled, removes the directory erased
555 if ( watch->Recursive ) {
556 Lock l( mWatchesLock );
557
558 for ( WatchMap::iterator it = mWatches.begin(); it != mWatches.end(); ++it ) {
559 if ( it->second->Directory == fpath ) {
560 removeWatchLocked( it->second->InotifyID );
561 break;
562 }
563 }
564 }
565 }
566}
567
568std::list<std::string> FileWatcherInotify::directories() {
569 std::list<std::string> dirs;
570
571 Lock l( mRealWatchesLock );
572
573 WatchMap::iterator it = mRealWatches.begin();
574
575 for ( ; it != mRealWatches.end(); ++it ) {
576 dirs.push_back( it->second->Directory );
577 }
578
579 return dirs;
580}
581
582bool FileWatcherInotify::pathInWatches( const std::string& path ) {
583 Lock l( mRealWatchesLock );
584
585 /// Search in the real watches, since it must allow adding a watch already watched as a subdir
586 WatchMap::iterator it = mRealWatches.begin();
587
588 for ( ; it != mRealWatches.end(); ++it ) {
589 if ( it->second->Directory == path ) {
590 return true;
591 }
592 }
593
594 return false;
595}
596
597} // namespace efsw
598
599#endif