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>
35 #include <sys/inotify.h>
36 #include <sys/utsname.h>
37 #include <sys/types.h>
46 const int EVENT_STRUCT_SIZE =
sizeof(
struct inotify_event );
50 const int EVENT_BUFFER_SIZE = EVENT_STRUCT_SIZE + 1024*16;
52 QByteArray stripTrailingSlash(
const QByteArray& path ) {
54 if ( p.endsWith(
'/' ) )
55 p.truncate( p.length()-1 );
59 QByteArray concatPath(
const QByteArray& p1,
const QByteArray& p2 ) {
61 if( p.isEmpty() || p[p.length()-1] !=
'/' )
68 class KInotify::Private
72 : userLimitReachedSignaled( false),
80 while( !dirIterators.isEmpty() )
81 delete dirIterators.takeFirst();
85 QTimer cookieExpireTimer;
87 bool userLimitReachedSignaled;
93 QSet<QByteArray> pathCache;
96 QLinkedList<QDirIterator*> dirIterators;
98 unsigned char eventBuffer[EVENT_BUFFER_SIZE];
105 if ( m_inotifyFd < 0 ) {
116 ::close( m_inotifyFd );
120 bool addWatch(
const QString& path ) {
121 WatchEvents newMode = mode;
122 WatchFlags newFlags = flags;
124 if( !q->filterWatch( path, newMode, newFlags ) ) {
130 const QByteArray encpath = QFile::encodeName( path );
131 int wd = inotify_add_watch( inotify(), encpath.data(), mask );
135 watchPathHash.insert( wd, normalized );
136 pathWatchHash.insert( normalized, wd );
140 kDebug() <<
"Failed to create watch for" << path;
142 if ( errno == ENOSPC ) {
144 kDebug() <<
"User limit reached. Count: " << watchPathHash.count();
145 userLimitReachedSignaled =
true;
146 emit q->watchUserLimitReached( path );
153 kDebug() << wd << watchPathHash[wd].toByteArray();
154 pathWatchHash.remove( watchPathHash.take( wd ) );
155 inotify_rm_watch( inotify(), wd );
161 bool _k_addWatches() {
162 bool addedWatchSuccessfully =
false;
167 if( userLimitReachedSignaled ){
171 if( !dirIterators.isEmpty() ) {
172 QDirIterator* it = dirIterators.front();
173 if( it->hasNext() ) {
174 QString dirPath = it->next();
177 QDirIterator* iter=
new QDirIterator( dirPath, QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks );
178 dirIterators.prepend( iter );
179 addedWatchSuccessfully =
true;
183 delete dirIterators.takeFirst();
188 if ( !dirIterators.isEmpty() ) {
189 QMetaObject::invokeMethod( q,
"_k_addWatches", Qt::QueuedConnection );
192 return addedWatchSuccessfully;
198 m_inotifyFd = inotify_init();
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 ) ) );
209 QSocketNotifier* m_notifier;
217 d( new Private( this ) )
221 d->cookieExpireTimer.setInterval( 1000 );
222 d->cookieExpireTimer.setSingleShot(
true );
223 connect( &d->cookieExpireTimer, SIGNAL(timeout()),
this, SLOT(slotClearCookies()) );
235 if( d->inotify() > 0 ) {
238 int major, minor, patch=0;
239 if ( uname(&uts) < 0 ) {
242 else if ( sscanf( uts.release,
"%d.%d.%d", &major, &minor, &patch) != 3 ) {
244 if ( sscanf( uts.release,
"%d.%d", &major, &minor) != 2 )
247 else if( major * 1000000 + minor * 1000 + patch < 2006014 ) {
248 kDebug(7001) <<
"Can't use INotify, Linux kernel too old";
262 const QByteArray p( stripTrailingSlash( QFile::encodeName( path ) ) );
268 d->userLimitReachedSignaled =
false;
279 if( d->userLimitReachedSignaled ) {
280 QDirIterator* iter =
new QDirIterator( path, QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks );
281 d->dirIterators.prepend( iter );
285 if(! ( d->addWatch( path ) ) )
287 QDirIterator* iter =
new QDirIterator( path, QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks );
288 d->dirIterators.prepend( iter );
289 return d->_k_addWatches();
296 QMutableLinkedListIterator<QDirIterator*> iter( d->dirIterators );
297 while( iter.hasNext() ) {
298 QDirIterator* dirIter = iter.next();
299 if( dirIter->path().startsWith( path ) ) {
306 QByteArray encodedPath( QFile::encodeName( path ) );
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 );
331 void KInotify::slotEvent(
int socket )
334 const int len = read( socket, d->eventBuffer, EVENT_BUFFER_SIZE );
336 while ( i < len && len-i >= EVENT_STRUCT_SIZE ) {
337 const struct inotify_event*
event = (
struct inotify_event* )&d->eventBuffer[i];
343 kError() <<
"Inotify - too many event - Overflowed";
350 path = d->watchPathHash.value( event->wd ).toByteArray();
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 );
359 Q_ASSERT( !path.isEmpty() ||
event->mask &
EventIgnored );
365 emit
accessed( QFile::decodeName(path) );
381 if ( event->mask & IN_ISDIR ) {
383 addWatch( path, d->mode, d->flags );
385 emit
created( QFile::decodeName(path), event->mask & IN_ISDIR );
388 kDebug() << path <<
"EventDeleteSelf";
389 d->removeWatch( event->wd );
390 emit
deleted( QFile::decodeName(path), event->mask & IN_ISDIR );
395 if( !(event->mask & IN_ISDIR) )
396 emit
deleted( QFile::decodeName(path),
false );
400 emit
modified( QFile::decodeName(path) );
404 kWarning() <<
"EventMoveSelf: THIS CASE IS NOT HANDLED PROPERLY!";
408 d->cookies[
event->cookie] = qMakePair( path, WatchFlags( event->mask ) );
409 d->cookieExpireTimer.start();
413 if ( d->cookies.contains( event->cookie ) ) {
414 const QByteArray oldPath = d->cookies.take(event->cookie).first;
417 if( event->mask & IN_ISDIR ) {
420 if( it != d->pathWatchHash.end() ) {
421 kDebug() << oldPath << path;
422 const int wd = it.value();
424 d->watchPathHash[wd] = optimPath;
425 d->pathWatchHash.erase(it);
426 d->pathWatchHash.insert( optimPath, wd );
430 emit
moved( QFile::decodeName(oldPath), QFile::decodeName(path) );
433 kDebug() <<
"No cookie for move information of" << path <<
"simulating new file event";
434 emit
created(path, event->mask & IN_ISDIR);
437 if(!(event->mask & IN_ISDIR)) {
444 emit
opened( QFile::decodeName(path) );
448 if ( event->mask & IN_ISDIR ) {
449 d->removeWatch( event->wd );
454 emit
unmounted( QFile::decodeName(path) );
459 kDebug() << path <<
"EventQueueOverflow";
466 i += EVENT_STRUCT_SIZE +
event->len;
470 kDebug() <<
"Failed to read event.";
474 void KInotify::slotClearCookies()
476 QHashIterator<int, QPair<QByteArray, WatchFlags> > it( d->cookies );
477 while( it.hasNext() ) {
480 emit
deleted( QFile::decodeName(it.value().first), it.value().second & IN_ISDIR );
487 #include "kinotify.moc"
File was opened (compare inotify's IN_OPEN)
Event queued overflowed (compare inotify's IN_Q_OVERFLOW)
Metadata changed (permissions, timestamps, extended attributes, etc., compare inotify's IN_ATTRIB) ...
A simple wrapper around inotify which only allows to add folders recursively.
void modified(const QString &file)
Emitted if a watched file is modified (KInotify::EventModify)
File was modified (compare inotify's IN_MODIFY)
Watched file/directory was itself moved (compare inotify's IN_MOVE_SELF)
This class holds a QByteArray which corresponds to a file url in a more memory efficient manner...
File/directory created in watched directory (compare inotify's IN_CREATE)
void deleted(const QString &file, bool isDir)
Emitted if a watched file or folder has been deleted.
void created(const QString &file, bool isDir)
Emitted if a new file has been created in one of the watched folders (KInotify::EventCreate) ...
void opened(const QString &file)
Emitted if a file is opened (KInotify::EventOpen)
bool watchingPath(const QString &path) const
void unmounted(const QString &file)
Emitted if a watched path has been unmounted (KInotify::EventUnmount)
Watched file/directory was itself deleted (compare inotify's IN_DELETE_SELF)
void moved(const QString &oldName, const QString &newName)
Emitted if a file or folder has been moved or renamed.
bool removeWatch(const QString &path)
void accessed(const QString &file)
Emitted if a file is accessed (KInotify::EventAccess)
virtual bool filterWatch(const QString &path, WatchEvents &modes, WatchFlags &flags)
Called for every folder that is being watched.
File opened for writing was closed (compare inotify's IN_CLOSE_WRITE)
void attributeChanged(const QString &file)
Emitted if file attributes are changed (KInotify::EventAttributeChange)
Backing fs was unmounted (compare inotify's IN_UNMOUNT)
void closedRead(const QString &file)
Emitted if FIXME (KInotify::EventCloseRead)
Do not generate events for unlinked files (IN_EXCL_UNLINK)
File moved into watched directory (compare inotify's IN_MOVED_TO)
File was accessed (read, compare inotify's IN_ACCESS)
void closedWrite(const QString &file)
Emitted if FIXME (KInotify::EventCloseWrite)
File not opened for writing was closed (compare inotify's IN_CLOSE_NOWRITE)
virtual bool addWatch(const QString &path, WatchEvents modes, WatchFlags flags=WatchFlags())
File was ignored (compare inotify's IN_IGNORED)
File moved out of watched directory (compare inotify's IN_MOVED_FROM)
KInotify(QObject *parent=0)
void resetUserLimit()
Call this when the inotify limit has been increased.