• Skip to content
  • Skip to link menu
KDE API Reference
  • KDE API Reference
  • kdelibs API Reference
  • KDE Home
  • Contact Us
 

Nepomuk-Core

  • sources
  • kde-4.12
  • kdelibs
  • nepomuk-core
  • services
  • filewatch
kinotify.cpp
Go to the documentation of this file.
1 /* This file is part of the KDE libraries
2  Copyright (C) 2007-2010 Sebastian Trueg <trueg@kde.org>
3  Copyright (C) 2012 Vishesh Handa <me@vhanda.in>
4 
5  This library is free software; you can redistribute it and/or
6  modify it under the terms of the GNU Library General Public
7  License as published by the Free Software Foundation; either
8  version 2 of the License, or (at your option) any later version.
9 
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  Library General Public License for more details.
14 
15  You should have received a copy of the GNU Library General Public License
16  along with this library; see the file COPYING.LIB. If not, write to
17  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  Boston, MA 02110-1301, USA.
19 */
20 
21 #include "kinotify.h"
22 #include "optimizedbytearray.h"
23 
24 #include <QtCore/QSocketNotifier>
25 #include <QtCore/QHash>
26 #include <QtCore/QDirIterator>
27 #include <QtCore/QFile>
28 #include <QtCore/QScopedArrayPointer>
29 #include <QtCore/QLinkedList>
30 #include <QtCore/QTimer>
31 #include <QtCore/QPair>
32 
33 #include <kdebug.h>
34 
35 #include <sys/inotify.h>
36 #include <sys/utsname.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <unistd.h>
40 #include <fcntl.h>
41 #include <errno.h>
42 #include <dirent.h>
43 
44 
45 namespace {
46  const int EVENT_STRUCT_SIZE = sizeof( struct inotify_event );
47 
48  // we need one event to fit into the buffer, the problem is that the name
49  // is a variable length array
50  const int EVENT_BUFFER_SIZE = EVENT_STRUCT_SIZE + 1024*16;
51 
52  QByteArray stripTrailingSlash( const QByteArray& path ) {
53  QByteArray p( path );
54  if ( p.endsWith( '/' ) )
55  p.truncate( p.length()-1 );
56  return p;
57  }
58 
59  QByteArray concatPath( const QByteArray& p1, const QByteArray& p2 ) {
60  QByteArray p(p1);
61  if( p.isEmpty() || p[p.length()-1] != '/' )
62  p.append('/');
63  p.append(p2);
64  return p;
65  }
66 }
67 
68 class KInotify::Private
69 {
70 public:
71  Private( KInotify* parent )
72  : userLimitReachedSignaled( false),
73  m_inotifyFd( -1 ),
74  m_notifier( 0 ),
75  q( parent) {
76  }
77 
78  ~Private() {
79  close();
80  while( !dirIterators.isEmpty() )
81  delete dirIterators.takeFirst();
82  }
83 
84  QHash<int, QPair<QByteArray, WatchFlags> > cookies;
85  QTimer cookieExpireTimer;
86  // This variable is set to true if the watch limit is reached, and reset when it is raised
87  bool userLimitReachedSignaled;
88 
89  // url <-> wd mappings
90  // Read the documentation fo OptimizedByteArray to understand why have a cache
91  QHash<int, OptimizedByteArray> watchPathHash;
92  QHash<OptimizedByteArray, int> pathWatchHash;
93  QSet<QByteArray> pathCache;
94 
96  QLinkedList<QDirIterator*> dirIterators;
97 
98  unsigned char eventBuffer[EVENT_BUFFER_SIZE];
99 
100  // FIXME: only stored from the last addWatch call
101  WatchEvents mode;
102  WatchFlags flags;
103 
104  int inotify() {
105  if ( m_inotifyFd < 0 ) {
106  open();
107  }
108  return m_inotifyFd;
109  }
110 
111  void close() {
112  kDebug();
113  delete m_notifier;
114  m_notifier = 0;
115 
116  ::close( m_inotifyFd );
117  m_inotifyFd = -1;
118  }
119 
120  bool addWatch( const QString& path ) {
121  WatchEvents newMode = mode;
122  WatchFlags newFlags = flags;
123  //Encode the path
124  if( !q->filterWatch( path, newMode, newFlags ) ) {
125  return false;
126  }
127  // we always need the unmount event to maintain our path hash
128  const int mask = newMode|newFlags|EventUnmount|FlagExclUnlink;
129 
130  const QByteArray encpath = QFile::encodeName( path );
131  int wd = inotify_add_watch( inotify(), encpath.data(), mask );
132  if ( wd > 0 ) {
133 // kDebug() << "Successfully added watch for" << path << watchPathHash.count();
134  OptimizedByteArray normalized( stripTrailingSlash( encpath ), pathCache );
135  watchPathHash.insert( wd, normalized );
136  pathWatchHash.insert( normalized, wd );
137  return true;
138  }
139  else {
140  kDebug() << "Failed to create watch for" << path;
141  //If we could not create the watch because we have hit the limit, try raising it.
142  if ( errno == ENOSPC ) {
143  //If we can't, fall back to signalling
144  kDebug() << "User limit reached. Count: " << watchPathHash.count();
145  userLimitReachedSignaled = true;
146  emit q->watchUserLimitReached( path );
147  }
148  return false;
149  }
150  }
151 
152  void removeWatch( int wd ) {
153  kDebug() << wd << watchPathHash[wd].toByteArray();
154  pathWatchHash.remove( watchPathHash.take( wd ) );
155  inotify_rm_watch( inotify(), wd );
156  }
157 
161  bool _k_addWatches() {
162  bool addedWatchSuccessfully = false;
163 
164  //Do nothing if the inotify user limit has been signaled.
165  //This means that we will not empty the dirIterators while
166  //waiting for authentication.
167  if( userLimitReachedSignaled ){
168  return false;
169  }
170 
171  if( !dirIterators.isEmpty() ) {
172  QDirIterator* it = dirIterators.front();
173  if( it->hasNext() ) {
174  QString dirPath = it->next();
175  if( addWatch( dirPath ) ) {
176  // IMPORTANT: We do not follow system links. Ever.
177  QDirIterator* iter= new QDirIterator( dirPath, QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks );
178  dirIterators.prepend( iter );
179  addedWatchSuccessfully = true;
180  }
181  }
182  else {
183  delete dirIterators.takeFirst();
184  }
185  }
186 
187  // asynchronously add the next batch
188  if ( !dirIterators.isEmpty() ) {
189  QMetaObject::invokeMethod( q, "_k_addWatches", Qt::QueuedConnection );
190  }
191 
192  return addedWatchSuccessfully;
193  }
194 
195 private:
196  void open() {
197  kDebug();
198  m_inotifyFd = inotify_init();
199  delete m_notifier;
200  if ( m_inotifyFd > 0 ) {
201  fcntl( m_inotifyFd, F_SETFD, FD_CLOEXEC );
202  kDebug() << "Successfully opened connection to inotify:" << m_inotifyFd;
203  m_notifier = new QSocketNotifier( m_inotifyFd, QSocketNotifier::Read );
204  connect( m_notifier, SIGNAL( activated( int ) ), q, SLOT( slotEvent( int ) ) );
205  }
206  }
207 
208  int m_inotifyFd;
209  QSocketNotifier* m_notifier;
210 
211  KInotify* q;
212 };
213 
214 
215 KInotify::KInotify( QObject* parent )
216  : QObject( parent ),
217  d( new Private( this ) )
218 {
219  // 1 second is more than enough time for the EventMoveTo event to occur
220  // after the EventMoveFrom event has occurred
221  d->cookieExpireTimer.setInterval( 1000 );
222  d->cookieExpireTimer.setSingleShot( true );
223  connect( &d->cookieExpireTimer, SIGNAL(timeout()), this, SLOT(slotClearCookies()) );
224 }
225 
226 
227 KInotify::~KInotify()
228 {
229  delete d;
230 }
231 
232 
233 bool KInotify::available() const
234 {
235  if( d->inotify() > 0 ) {
236  // trueg: Copied from KDirWatch.
237  struct utsname uts;
238  int major, minor, patch=0;
239  if ( uname(&uts) < 0 ) {
240  return false; // *shrug*
241  }
242  else if ( sscanf( uts.release, "%d.%d.%d", &major, &minor, &patch) != 3 ) {
243  //Kernels > 3.0 can in principle have two-number versions.
244  if ( sscanf( uts.release, "%d.%d", &major, &minor) != 2 )
245  return false; // *shrug*
246  }
247  else if( major * 1000000 + minor * 1000 + patch < 2006014 ) { // <2.6.14
248  kDebug(7001) << "Can't use INotify, Linux kernel too old";
249  return false;
250  }
251 
252  return true;
253  }
254  else {
255  return false;
256  }
257 }
258 
259 
260 bool KInotify::watchingPath( const QString& path ) const
261 {
262  const QByteArray p( stripTrailingSlash( QFile::encodeName( path ) ) );
263  return d->pathWatchHash.contains( OptimizedByteArray(p, d->pathCache) );
264 }
265 
266 void KInotify::resetUserLimit()
267 {
268  d->userLimitReachedSignaled = false;
269 }
270 
271 bool KInotify::addWatch( const QString& path, WatchEvents mode, WatchFlags flags )
272 {
273  kDebug() << path;
274 
275  d->mode = mode;
276  d->flags = flags;
277  //If the inotify user limit has been signaled,
278  //just queue this folder for watching.
279  if( d->userLimitReachedSignaled ) {
280  QDirIterator* iter = new QDirIterator( path, QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks );
281  d->dirIterators.prepend( iter );
282  return false;
283  }
284 
285  if(! ( d->addWatch( path ) ) )
286  return false;
287  QDirIterator* iter = new QDirIterator( path, QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks );
288  d->dirIterators.prepend( iter );
289  return d->_k_addWatches();
290 }
291 
292 
293 bool KInotify::removeWatch( const QString& path )
294 {
295  // Stop all of the dirIterators which contain path
296  QMutableLinkedListIterator<QDirIterator*> iter( d->dirIterators );
297  while( iter.hasNext() ) {
298  QDirIterator* dirIter = iter.next();
299  if( dirIter->path().startsWith( path ) ) {
300  iter.remove();
301  delete dirIter;
302  }
303  }
304 
305  // Remove all the watches
306  QByteArray encodedPath( QFile::encodeName( path ) );
307  QHash<int, OptimizedByteArray>::iterator it = d->watchPathHash.begin();
308  while ( it != d->watchPathHash.end() ) {
309  if ( it.value().toByteArray().startsWith( encodedPath ) ) {
310  inotify_rm_watch( d->inotify(), it.key() );
311  d->pathWatchHash.remove(it.value());
312  it = d->watchPathHash.erase( it );
313  }
314  else {
315  ++it;
316  }
317  }
318  return true;
319 }
320 
321 
322 bool KInotify::filterWatch( const QString & path, WatchEvents & modes, WatchFlags & flags )
323 {
324  Q_UNUSED( path );
325  Q_UNUSED( modes );
326  Q_UNUSED( flags );
327  return true;
328 }
329 
330 
331 void KInotify::slotEvent( int socket )
332 {
333  // read at least one event
334  const int len = read( socket, d->eventBuffer, EVENT_BUFFER_SIZE );
335  int i = 0;
336  while ( i < len && len-i >= EVENT_STRUCT_SIZE ) {
337  const struct inotify_event* event = ( struct inotify_event* )&d->eventBuffer[i];
338 
339  QByteArray path;
340 
341  // Overflow happens sometimes if we process the events too slowly
342  if( event->wd < 0 && (event->mask & EventQueueOverflow) ) {
343  kError() << "Inotify - too many event - Overflowed";
344  return;
345  }
346 
347  // the event name only contains an interesting value if we get an event for a file/folder inside
348  // a watched folder. Otherwise we should ignore it
349  if ( event->mask & (EventDeleteSelf|EventMoveSelf) ) {
350  path = d->watchPathHash.value( event->wd ).toByteArray();
351  }
352  else {
353  // we cannot use event->len here since it contains the size of the buffer and not the length of the string
354  const QByteArray eventName = QByteArray::fromRawData( event->name, qstrnlen(event->name,event->len) );
355  const QByteArray hashedPath = d->watchPathHash.value( event->wd ).toByteArray();
356  path = concatPath( hashedPath, eventName );
357  }
358 
359  Q_ASSERT( !path.isEmpty() || event->mask & EventIgnored );
360  Q_ASSERT( path != "/" || event->mask & EventIgnored || event->mask & EventUnmount);
361 
362  // now signal the event
363  if ( event->mask & EventAccess) {
364 // kDebug() << path << "EventAccess";
365  emit accessed( QFile::decodeName(path) );
366  }
367  if ( event->mask & EventAttributeChange ) {
368 // kDebug() << path << "EventAttributeChange";
369  emit attributeChanged( QFile::decodeName(path) );
370  }
371  if ( event->mask & EventCloseWrite ) {
372 // kDebug() << path << "EventCloseWrite";
373  emit closedWrite( QFile::decodeName(path) );
374  }
375  if ( event->mask & EventCloseRead ) {
376 // kDebug() << path << "EventCloseRead";
377  emit closedRead( QFile::decodeName(path) );
378  }
379  if ( event->mask & EventCreate ) {
380 // kDebug() << path << "EventCreate";
381  if ( event->mask & IN_ISDIR ) {
382  // FIXME: store the mode and flags somewhere
383  addWatch( path, d->mode, d->flags );
384  }
385  emit created( QFile::decodeName(path), event->mask & IN_ISDIR );
386  }
387  if ( event->mask & EventDeleteSelf ) {
388  kDebug() << path << "EventDeleteSelf";
389  d->removeWatch( event->wd );
390  emit deleted( QFile::decodeName(path), event->mask & IN_ISDIR );
391  }
392  if ( event->mask & EventDelete ) {
393 // kDebug() << path << "EventDelete";
394  // we watch all folders recursively. Thus, folder removing is reported in DeleteSelf.
395  if( !(event->mask & IN_ISDIR) )
396  emit deleted( QFile::decodeName(path), false );
397  }
398  if ( event->mask & EventModify ) {
399 // kDebug() << path << "EventModify";
400  emit modified( QFile::decodeName(path) );
401  }
402  if ( event->mask & EventMoveSelf ) {
403 // kDebug() << path << "EventMoveSelf";
404  kWarning() << "EventMoveSelf: THIS CASE IS NOT HANDLED PROPERLY!";
405  }
406  if ( event->mask & EventMoveFrom ) {
407 // kDebug() << path << "EventMoveFrom";
408  d->cookies[event->cookie] = qMakePair( path, WatchFlags( event->mask ) );
409  d->cookieExpireTimer.start();
410  }
411  if ( event->mask & EventMoveTo ) {
412  // check if we have a cookie for this one
413  if ( d->cookies.contains( event->cookie ) ) {
414  const QByteArray oldPath = d->cookies.take(event->cookie).first;
415 
416  // update the path cache
417  if( event->mask & IN_ISDIR ) {
418  OptimizedByteArray optimOldPath( oldPath, d->pathCache );
419  QHash<OptimizedByteArray, int>::iterator it = d->pathWatchHash.find( optimOldPath );
420  if( it != d->pathWatchHash.end() ) {
421  kDebug() << oldPath << path;
422  const int wd = it.value();
423  OptimizedByteArray optimPath( path, d->pathCache );
424  d->watchPathHash[wd] = optimPath;
425  d->pathWatchHash.erase(it);
426  d->pathWatchHash.insert( optimPath, wd );
427  }
428  }
429 // kDebug() << oldPath << "EventMoveTo" << path;
430  emit moved( QFile::decodeName(oldPath), QFile::decodeName(path) );
431  }
432  else {
433  kDebug() << "No cookie for move information of" << path << "simulating new file event";
434  emit created(path, event->mask & IN_ISDIR);
435 
436  // also simulate a closed write since that is what triggers indexing of files in the file watcher
437  if(!(event->mask & IN_ISDIR)) {
438  emit closedWrite(path);
439  }
440  }
441  }
442  if ( event->mask & EventOpen ) {
443 // kDebug() << path << "EventOpen";
444  emit opened( QFile::decodeName(path) );
445  }
446  if ( event->mask & EventUnmount ) {
447 // kDebug() << path << "EventUnmount. removing from path hash";
448  if ( event->mask & IN_ISDIR ) {
449  d->removeWatch( event->wd );
450  }
451  // This is present because a unmount event is sent by inotify after unmounting, by
452  // which time the watches have already been removed.
453  if( path != "/"){
454  emit unmounted( QFile::decodeName(path) );
455  }
456  }
457  if ( event->mask & EventQueueOverflow ) {
458  // This should not happen since we grab all events as soon as they arrive
459  kDebug() << path << "EventQueueOverflow";
460 // emit queueOverflow();
461  }
462  if ( event->mask & EventIgnored ) {
463 // kDebug() << path << "EventIgnored";
464  }
465 
466  i += EVENT_STRUCT_SIZE + event->len;
467  }
468 
469  if ( len < 0 ) {
470  kDebug() << "Failed to read event.";
471  }
472 }
473 
474 void KInotify::slotClearCookies()
475 {
476  QHashIterator<int, QPair<QByteArray, WatchFlags> > it( d->cookies );
477  while( it.hasNext() ) {
478  it.next();
479  removeWatch( it.value().first );
480  emit deleted( QFile::decodeName(it.value().first), it.value().second & IN_ISDIR );
481  }
482 
483  d->cookies.clear();
484 }
485 
486 
487 #include "kinotify.moc"
KInotify::EventOpen
File was opened (compare inotify's IN_OPEN)
Definition: kinotify.h:59
KInotify::EventQueueOverflow
Event queued overflowed (compare inotify's IN_Q_OVERFLOW)
Definition: kinotify.h:61
KInotify::EventAttributeChange
Metadata changed (permissions, timestamps, extended attributes, etc., compare inotify's IN_ATTRIB) ...
Definition: kinotify.h:49
KInotify
A simple wrapper around inotify which only allows to add folders recursively.
Definition: kinotify.h:33
KInotify::modified
void modified(const QString &file)
Emitted if a watched file is modified (KInotify::EventModify)
KInotify::EventModify
File was modified (compare inotify's IN_MODIFY)
Definition: kinotify.h:55
KInotify::EventMoveSelf
Watched file/directory was itself moved (compare inotify's IN_MOVE_SELF)
Definition: kinotify.h:56
OptimizedByteArray
This class holds a QByteArray which corresponds to a file url in a more memory efficient manner...
Definition: optimizedbytearray.h:44
KInotify::EventDelete
File/directory created in watched directory (compare inotify's IN_CREATE)
Definition: kinotify.h:53
KInotify::deleted
void deleted(const QString &file, bool isDir)
Emitted if a watched file or folder has been deleted.
KInotify::created
void created(const QString &file, bool isDir)
Emitted if a new file has been created in one of the watched folders (KInotify::EventCreate) ...
QHash
QObject
KInotify::EventCreate
Definition: kinotify.h:52
KInotify::opened
void opened(const QString &file)
Emitted if a file is opened (KInotify::EventOpen)
KInotify::watchingPath
bool watchingPath(const QString &path) const
Definition: kinotify.cpp:260
KInotify::unmounted
void unmounted(const QString &file)
Emitted if a watched path has been unmounted (KInotify::EventUnmount)
KInotify::EventDeleteSelf
Watched file/directory was itself deleted (compare inotify's IN_DELETE_SELF)
Definition: kinotify.h:54
KInotify::moved
void moved(const QString &oldName, const QString &newName)
Emitted if a file or folder has been moved or renamed.
KInotify::removeWatch
bool removeWatch(const QString &path)
Definition: kinotify.cpp:293
KInotify::accessed
void accessed(const QString &file)
Emitted if a file is accessed (KInotify::EventAccess)
optimizedbytearray.h
KInotify::filterWatch
virtual bool filterWatch(const QString &path, WatchEvents &modes, WatchFlags &flags)
Called for every folder that is being watched.
Definition: kinotify.cpp:322
KInotify::EventCloseWrite
File opened for writing was closed (compare inotify's IN_CLOSE_WRITE)
Definition: kinotify.h:50
KInotify::attributeChanged
void attributeChanged(const QString &file)
Emitted if file attributes are changed (KInotify::EventAttributeChange)
KInotify::EventUnmount
Backing fs was unmounted (compare inotify's IN_UNMOUNT)
Definition: kinotify.h:60
KInotify::closedRead
void closedRead(const QString &file)
Emitted if FIXME (KInotify::EventCloseRead)
KInotify::~KInotify
virtual ~KInotify()
Definition: kinotify.cpp:227
KInotify::available
bool available() const
Definition: kinotify.cpp:233
KInotify::FlagExclUnlink
Do not generate events for unlinked files (IN_EXCL_UNLINK)
Definition: kinotify.h:88
KInotify::EventMoveTo
File moved into watched directory (compare inotify's IN_MOVED_TO)
Definition: kinotify.h:58
KInotify::EventAccess
File was accessed (read, compare inotify's IN_ACCESS)
Definition: kinotify.h:48
KInotify::closedWrite
void closedWrite(const QString &file)
Emitted if FIXME (KInotify::EventCloseWrite)
KInotify::EventCloseRead
File not opened for writing was closed (compare inotify's IN_CLOSE_NOWRITE)
Definition: kinotify.h:51
KInotify::addWatch
virtual bool addWatch(const QString &path, WatchEvents modes, WatchFlags flags=WatchFlags())
Definition: kinotify.cpp:271
KInotify::EventIgnored
File was ignored (compare inotify's IN_IGNORED)
Definition: kinotify.h:62
KInotify::EventMoveFrom
File moved out of watched directory (compare inotify's IN_MOVED_FROM)
Definition: kinotify.h:57
KInotify::KInotify
KInotify(QObject *parent=0)
Definition: kinotify.cpp:215
KInotify::resetUserLimit
void resetUserLimit()
Call this when the inotify limit has been increased.
Definition: kinotify.cpp:266
kinotify.h
This file is part of the KDE documentation.
Documentation copyright © 1996-2014 The KDE developers.
Generated on Tue Oct 14 2014 22:48:08 by doxygen 1.8.7 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

Nepomuk-Core

Skip menu "Nepomuk-Core"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Modules
  • Related Pages

kdelibs API Reference

Skip menu "kdelibs API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  • kjsembed
  •   WTF
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Nepomuk-Core
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver

Search



Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal