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

KIO

kdirwatch.cpp

Go to the documentation of this file.
00001 // -*- c-basic-offset: 2 -*-
00002 /* This file is part of the KDE libraries
00003    Copyright (C) 1998 Sven Radej <sven@lisa.exp.univie.ac.at>
00004    Copyright (C) 2006 Dirk Mueller <mueller@kde.org>
00005    Copyright (C) 2007 Flavio Castelli <flavio.castelli@gmail.com>
00006    Copyright (C) 2008 Rafal Rzepecki <divided.mind@gmail.com>
00007 
00008    This library is free software; you can redistribute it and/or
00009    modify it under the terms of the GNU Library General Public
00010    License version 2 as published by the Free Software Foundation.
00011 
00012    This library is distributed in the hope that it will be useful,
00013    but WITHOUT ANY WARRANTY; without even the implied warranty of
00014    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00015    Library General Public License for more details.
00016 
00017    You should have received a copy of the GNU Library General Public License
00018    along with this library; see the file COPYING.LIB.  If not, write to
00019    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00020    Boston, MA 02110-1301, USA.
00021 */
00022 
00023 
00024 // CHANGES:
00025 // Jul 30, 2008 - Don't follow symlinks when recursing to avoid loops (Rafal)
00026 // Aug 6,  2007 - KDirWatch::WatchModes support complete, flags work fine also
00027 // when using FAMD (Flavio Castelli)
00028 // Aug 3,  2007 - Handled KDirWatch::WatchModes flags when using inotify, now
00029 // recursive and file monitoring modes are implemented (Flavio Castelli)
00030 // Jul 30, 2007 - Substituted addEntry boolean params with KDirWatch::WatchModes
00031 // flag (Flavio Castelli)
00032 // Oct 4,  2005 - Inotify support (Dirk Mueller)
00033 // Februar 2002 - Add file watching and remote mount check for STAT
00034 // Mar 30, 2001 - Native support for Linux dir change notification.
00035 // Jan 28, 2000 - Usage of FAM service on IRIX (Josef.Weidendorfer@in.tum.de)
00036 // May 24. 1998 - List of times introduced, and some bugs are fixed. (sven)
00037 // May 23. 1998 - Removed static pointer - you can have more instances.
00038 // It was Needed for KRegistry. KDirWatch now emits signals and doesn't
00039 // call (or need) KFM. No more URL's - just plain paths. (sven)
00040 // Mar 29. 1998 - added docs, stop/restart for particular Dirs and
00041 // deep copies for list of dirs. (sven)
00042 // Mar 28. 1998 - Created.  (sven)
00043 
00044 #include "kdirwatch.h"
00045 #include "kdirwatch_p.h"
00046 
00047 #include <config-kdirwatch.h>
00048 #include <config.h>
00049 
00050 #include <sys/stat.h>
00051 #include <assert.h>
00052 #include <QtCore/QDir>
00053 #include <QtCore/QFile>
00054 #include <QtCore/QSocketNotifier>
00055 #include <QtCore/QTimer>
00056 
00057 #include <kapplication.h>
00058 #include <kdebug.h>
00059 #include <kconfig.h>
00060 #include <kglobal.h>
00061 #include <kde_file.h>
00062 #include <kconfiggroup.h>
00063 #include "kmountpoint.h"
00064 
00065 #include <stdlib.h>
00066 
00067 // debug
00068 #include <sys/ioctl.h>
00069 
00070 
00071 #include <sys/utsname.h>
00072 
00073 #define NO_NOTIFY (time_t) 0
00074 
00075 static KDirWatchPrivate* dwp_self = 0;
00076 static KDirWatchPrivate* createPrivate() {
00077   if (!dwp_self)
00078     dwp_self = new KDirWatchPrivate;
00079   return dwp_self;
00080 }
00081 
00082 
00083 // Convert a string into a WatchMethod
00084 static KDirWatchPrivate::WatchMethod methodFromString(const QString& method) {
00085   if (method == "Fam") {
00086     return KDirWatchPrivate::Fam;
00087   } else if (method == "Stat") {
00088     return KDirWatchPrivate::Stat;
00089   } else if (method == "QFSWatch") {
00090     return KDirWatchPrivate::QFSWatch;
00091   } else {
00092 #ifdef Q_OS_WIN
00093     return KDirWatchPrivate::QFSWatch;
00094 #elif defined(Q_OS_FREEBSD)
00095     return KDirWatchPrivate::Stat;
00096 #else
00097     return KDirWatchPrivate::INotify;
00098 #endif
00099   }
00100 }
00101 
00102 
00103 //
00104 // Class KDirWatchPrivate (singleton)
00105 //
00106 
00107 /* All entries (files/directories) to be watched in the
00108  * application (coming from multiple KDirWatch instances)
00109  * are registered in a single KDirWatchPrivate instance.
00110  *
00111  * At the moment, the following methods for file watching
00112  * are supported:
00113  * - Polling: All files to be watched are polled regularly
00114  *   using stat (more precise: QFileInfo.lastModified()).
00115  *   The polling frequency is determined from global kconfig
00116  *   settings, defaulting to 500 ms for local directories
00117  *   and 5000 ms for remote mounts
00118  * - FAM (File Alternation Monitor): first used on IRIX, SGI
00119  *   has ported this method to LINUX. It uses a kernel part
00120  *   (IMON, sending change events to /dev/imon) and a user
00121  *   level damon (fam), to which applications connect for
00122  *   notification of file changes. For NFS, the fam damon
00123  *   on the NFS server machine is used; if IMON is not built
00124  *   into the kernel, fam uses polling for local files.
00125  * - INOTIFY: In LINUX 2.6.13, inode change notification was
00126  *   introduced. You're now able to watch arbitrary inode's
00127  *   for changes, and even get notification when they're
00128  *   unmounted.
00129  */
00130 
00131 KDirWatchPrivate::KDirWatchPrivate()
00132   : timer(),
00133     freq( 3600000 ), // 1 hour as upper bound
00134     statEntries( 0 ),
00135     m_ref( 0 ),
00136     delayRemove( false ),
00137     rescan_all( false ),
00138     rescan_timer()
00139 {
00140   timer.setObjectName( "KDirWatchPrivate::timer" );
00141   connect (&timer, SIGNAL(timeout()), this, SLOT(slotRescan()));
00142 
00143   KConfigGroup config(KGlobal::config(), "DirWatch");
00144   m_nfsPollInterval = config.readEntry("NFSPollInterval", 5000);
00145   m_PollInterval = config.readEntry("PollInterval", 500);
00146 
00147   QString method = config.readEntry("PreferredMethod", "inotify");
00148   m_preferredMethod = methodFromString(method);
00149 
00150   // The nfs method defaults to the normal (local) method
00151   m_nfsPreferredMethod = methodFromString(config.readEntry("nfsPreferredMethod", method));
00152 
00153   QStringList availableMethods;
00154 
00155   availableMethods << "Stat";
00156 
00157   // used for FAM
00158   rescan_timer.setObjectName( "KDirWatchPrivate::rescan_timer" );
00159   rescan_timer.setSingleShot( true );
00160   connect(&rescan_timer, SIGNAL(timeout()), this, SLOT(slotRescan()));
00161 
00162 #ifdef HAVE_FAM
00163   // It's possible that FAM server can't be started
00164   if (FAMOpen(&fc) ==0) {
00165     availableMethods << "FAM";
00166     use_fam=true;
00167     sn = new QSocketNotifier( FAMCONNECTION_GETFD(&fc),
00168                   QSocketNotifier::Read, this);
00169     connect( sn, SIGNAL(activated(int)),
00170          this, SLOT(famEventReceived()) );
00171   }
00172   else {
00173     kDebug(7001) << "Can't use FAM (fam daemon not running?)";
00174     use_fam=false;
00175   }
00176 #endif
00177 
00178 #ifdef HAVE_SYS_INOTIFY_H
00179   supports_inotify = true;
00180 
00181   m_inotify_fd = inotify_init();
00182 
00183   if ( m_inotify_fd <= 0 ) {
00184     kDebug(7001) << "Can't use Inotify, kernel doesn't support it";
00185     supports_inotify = false;
00186   }
00187 
00188   {
00189     struct utsname uts;
00190     int major, minor, patch;
00191     if (uname(&uts) < 0)
00192       supports_inotify = false; // *shrug*
00193     else if (sscanf(uts.release, "%d.%d.%d", &major, &minor, &patch) != 3)
00194       supports_inotify = false; // *shrug*
00195     else if( major * 1000000 + minor * 1000 + patch < 2006014 ) { // <2.6.14
00196       kDebug(7001) << "Can't use INotify, Linux kernel too old";
00197       supports_inotify = false;
00198     }
00199   }
00200 
00201   if ( supports_inotify ) {
00202     availableMethods << "INotify";
00203     fcntl(m_inotify_fd, F_SETFD, FD_CLOEXEC);
00204 
00205     mSn = new QSocketNotifier( m_inotify_fd, QSocketNotifier::Read, this );
00206     connect( mSn, SIGNAL(activated( int )),
00207              this, SLOT( inotifyEventReceived() ) );
00208   }
00209 #endif
00210 #ifdef HAVE_QFILESYSTEMWATCHER
00211   availableMethods << "QFileSystemWatcher";
00212   fsWatcher = new KFileSystemWatcher();
00213   connect(fsWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(fswEventReceived(QString)));
00214   connect(fsWatcher, SIGNAL(fileChanged(QString)),      this, SLOT(fswEventReceived(QString)));
00215 #endif
00216   kDebug(7001) << "Available methods: " << availableMethods;
00217 }
00218 
00219 /* This is called on app exit (K_GLOBAL_STATIC) */
00220 KDirWatchPrivate::~KDirWatchPrivate()
00221 {
00222   timer.stop();
00223 
00224   /* remove all entries being watched */
00225   removeEntries(0);
00226 
00227 #ifdef HAVE_FAM
00228   if (use_fam) {
00229     FAMClose(&fc);
00230   }
00231 #endif
00232 #ifdef HAVE_SYS_INOTIFY_H
00233   if ( supports_inotify )
00234     ::close( m_inotify_fd );
00235 #endif
00236 #ifdef HAVE_QFILESYSTEMWATCHER
00237   delete fsWatcher;
00238 #endif
00239 }
00240 
00241 void KDirWatchPrivate::inotifyEventReceived()
00242 {
00243   //kDebug(7001);
00244 #ifdef HAVE_SYS_INOTIFY_H
00245   if ( !supports_inotify )
00246     return;
00247 
00248   int pending = -1;
00249   int offset = 0;
00250   char buf[4096];
00251   assert( m_inotify_fd > -1 );
00252   ioctl( m_inotify_fd, FIONREAD, &pending );
00253 
00254   while ( pending > 0 ) {
00255 
00256     if ( pending > (int)sizeof( buf ) )
00257       pending = sizeof( buf );
00258 
00259     pending = read( m_inotify_fd, buf, pending);
00260 
00261     while ( pending > 0 ) {
00262       struct inotify_event *event = (struct inotify_event *) &buf[offset];
00263       pending -= sizeof( struct inotify_event ) + event->len;
00264       offset += sizeof( struct inotify_event ) + event->len;
00265 
00266       QString path;
00267       QByteArray cpath(event->name, event->len);
00268       if(event->len)
00269         path = QFile::decodeName ( cpath );
00270 
00271       if ( path.length() && isNoisyFile( cpath ) )
00272         continue;
00273 
00274       // now we're in deep trouble of finding the
00275       // associated entries
00276       // for now, we suck and iterate
00277       for ( EntryMap::Iterator it = m_mapEntries.begin();
00278             it != m_mapEntries.end();  ) {
00279         Entry* e = &( *it );
00280         ++it;
00281         if ( e->wd == event->wd ) {
00282           e->dirty = true;
00283 
00284           if( event->mask & IN_DELETE_SELF) {
00285             kDebug(7001) << "-->got deleteself signal for" << e->path;
00286             e->m_status = NonExistent;
00287             if (e->isDir)
00288               addEntry(0, QDir::cleanPath(e->path+"/.."), e, true);
00289             else
00290               addEntry(0, QFileInfo(e->path).absolutePath(), e, true);
00291           }
00292           if ( event->mask & IN_IGNORED ) {
00293             // Causes bug #207361 with kernels 2.6.31 and 2.6.32!
00294             //e->wd = -1;
00295           }
00296           if ( event->mask & (IN_CREATE|IN_MOVED_TO) ) {
00297             Entry* sub_entry = e->findSubEntry(e->path + '/' + path);
00298 
00299             if (sub_entry /*&& sub_entry->isDir*/) {
00300               removeEntry(0, e, sub_entry);
00301               //KDE_struct_stat stat_buf;
00302               //QByteArray tpath = QFile::encodeName(path);
00303               //KDE_stat(tpath, &stat_buf);
00304 
00305               //sub_entry->isDir = S_ISDIR(stat_buf.st_mode);
00306               //sub_entry->m_ctime = stat_buf.st_ctime;
00307               //sub_entry->m_status = Normal;
00308               //sub_entry->m_nlink = stat_buf.st_nlink;
00309 
00310               if(!useINotify(sub_entry))
00311                 useStat(sub_entry);
00312               sub_entry->dirty = true;
00313             }
00314             else if ((e->isDir) && (!e->m_clients.empty())) {
00315 
00316               const QString tpath = e->path + QLatin1Char('/') + path;
00317               bool isDir = false;
00318               const QList<Client *> clients = e->clientsForFileOrDir(tpath, &isDir);
00319               Q_FOREACH(Client *client, clients) {
00320                 // See discussion in addEntry for why we don't addEntry for individual
00321                 // files in WatchFiles mode with inotify.
00322                 if (isDir) {
00323                   addEntry(client->instance, tpath, 0, isDir,
00324                            isDir ? client->m_watchModes : KDirWatch::WatchDirOnly);
00325                 }
00326               }
00327               if (!clients.isEmpty()) {
00328                 emitEvent(e, Created, e->path+'/'+path);
00329                 kDebug(7001).nospace() << clients.count() << " instance(s) monitoring the new "
00330                                        << (isDir ? "dir " : "file ") << tpath;
00331               }
00332             }
00333           }
00334           if (event->mask & (IN_DELETE|IN_MOVED_FROM)) {
00335             if ((e->isDir) && (!e->m_clients.empty())) {
00336               Client* client = 0;
00337               // A file in this directory has been removed.  It wasn't an explicitly
00338               // watched file as it would have its own watch descriptor, so
00339               // no addEntry/ removeEntry bookkeeping should be required.  Emit
00340               // the event immediately if any clients are interested.
00341               KDE_struct_stat stat_buf;
00342               QString tpath = e->path + QLatin1Char('/') + path;
00343               // Unlike clientsForFileOrDir, the stat can fail here (item deleted),
00344               // so in that case we'll just take both kinds of clients and emit Deleted.
00345               KDirWatch::WatchModes flag = KDirWatch::WatchSubDirs | KDirWatch::WatchFiles;
00346               if (KDE::stat(tpath, &stat_buf) == 0) {
00347                 bool isDir = S_ISDIR(stat_buf.st_mode);
00348                 flag = isDir ? KDirWatch::WatchSubDirs : KDirWatch::WatchFiles;
00349               }
00350               int counter = 0;
00351               Q_FOREACH(client, e->m_clients) {
00352                   if (client->m_watchModes & flag) {
00353                         counter++;
00354                   }
00355               }
00356               if (counter != 0) {
00357                   emitEvent (e, Deleted, e->path+'/'+path);
00358               }
00359             }
00360           }
00361           if (event->mask & (IN_MODIFY|IN_ATTRIB)) {
00362             if ((e->isDir) && (!e->m_clients.empty())) {
00363               // A file in this directory has been changed.  No
00364               // addEntry/ removeEntry bookkeeping should be required.
00365               // Add the path to the list of pending file changes if
00366               // there are any interested clients.
00367               //KDE_struct_stat stat_buf;
00368               //QByteArray tpath = QFile::encodeName(e->path+'/'+path);
00369               //KDE_stat(tpath, &stat_buf);
00370               //bool isDir = S_ISDIR(stat_buf.st_mode);
00371 
00372               // The API doc is somewhat vague as to whether we should emit
00373               // dirty() for implicitly watched files when WatchFiles has
00374               // not been specified - we'll assume they are always interested,
00375               // regardless.
00376               // Don't worry about duplicates for the time
00377               // being; this is handled in slotRescan.
00378               e->m_pendingFileChanges.append(e->path+'/'+path);
00379             }
00380           }
00381 
00382           if (!rescan_timer.isActive())
00383             rescan_timer.start(m_PollInterval); // singleshot
00384 
00385           break;
00386         }
00387       }
00388     }
00389   }
00390 #endif
00391 }
00392 
00393 /* In FAM mode, only entries which are marked dirty are scanned.
00394  * We first need to mark all yet nonexistent, but possible created
00395  * entries as dirty...
00396  */
00397 void KDirWatchPrivate::Entry::propagate_dirty()
00398 {
00399   foreach(Entry *sub_entry, m_entries)
00400   {
00401      if (!sub_entry->dirty)
00402      {
00403         sub_entry->dirty = true;
00404         sub_entry->propagate_dirty();
00405      }
00406   }
00407 }
00408 
00409 
00410 /* A KDirWatch instance is interested in getting events for
00411  * this file/Dir entry.
00412  */
00413 void KDirWatchPrivate::Entry::addClient(KDirWatch* instance,
00414                                         KDirWatch::WatchModes watchModes)
00415 {
00416   if (instance == 0)
00417     return;
00418 
00419   foreach(Client* client, m_clients) {
00420     if (client->instance == instance) {
00421       client->count++;
00422       client->m_watchModes = watchModes;
00423       return;
00424     }
00425   }
00426 
00427   Client* client = new Client;
00428   client->instance = instance;
00429   client->count = 1;
00430   client->watchingStopped = instance->isStopped();
00431   client->pending = NoChange;
00432   client->m_watchModes = watchModes;
00433 
00434   m_clients.append(client);
00435 }
00436 
00437 void KDirWatchPrivate::Entry::removeClient(KDirWatch* instance)
00438 {
00439   QList<Client *>::iterator it = m_clients.begin();
00440   const QList<Client *>::iterator end = m_clients.end();
00441   for ( ; it != end ; ++it ) {
00442     Client* client = *it;
00443     if (client->instance == instance) {
00444       client->count--;
00445       if (client->count == 0) {
00446         m_clients.erase(it);
00447         delete client;
00448       }
00449       return;
00450     }
00451   }
00452 }
00453 
00454 /* get number of clients */
00455 int KDirWatchPrivate::Entry::clients()
00456 {
00457   int clients = 0;
00458   foreach(Client* client, m_clients)
00459     clients += client->count;
00460 
00461   return clients;
00462 }
00463 
00464 QList<KDirWatchPrivate::Client *> KDirWatchPrivate::Entry::clientsForFileOrDir(const QString& tpath, bool* isDir) const
00465 {
00466   QList<Client *> ret;
00467   KDE_struct_stat stat_buf;
00468   if (KDE::stat(tpath, &stat_buf) == 0) {
00469     *isDir = S_ISDIR(stat_buf.st_mode);
00470     const KDirWatch::WatchModes flag =
00471       *isDir ? KDirWatch::WatchSubDirs : KDirWatch::WatchFiles;
00472     Q_FOREACH(Client *client, this->m_clients) {
00473       if (client->m_watchModes & flag) {
00474         ret.append(client);
00475       }
00476     }
00477   } else {
00478     // Happens frequently, e.g. ERROR: couldn't stat "/home/dfaure/.viminfo.tmp"
00479     //kDebug(7001) << "ERROR: couldn't stat" << tpath;
00480   }
00481   // If KDE_stat fails then isDir is not set, but ret is empty anyway
00482   // so isDir won't be used.
00483   return ret;
00484 }
00485 
00486 KDirWatchPrivate::Entry* KDirWatchPrivate::entry(const QString& _path)
00487 {
00488 // we only support absolute paths
00489   if (_path.isEmpty() || QDir::isRelativePath(_path)) {
00490     return 0;
00491   }
00492 
00493   QString path (_path);
00494 
00495   if ( path.length() > 1 && path.endsWith( QLatin1Char( '/' ) ) )
00496     path.truncate( path.length() - 1 );
00497 
00498   EntryMap::Iterator it = m_mapEntries.find( path );
00499   if ( it == m_mapEntries.end() )
00500     return 0;
00501   else
00502     return &(*it);
00503 }
00504 
00505 // set polling frequency for a entry and adjust global freq if needed
00506 void KDirWatchPrivate::useFreq(Entry* e, int newFreq)
00507 {
00508   e->freq = newFreq;
00509 
00510   // a reasonable frequency for the global polling timer
00511   if (e->freq < freq) {
00512     freq = e->freq;
00513     if (timer.isActive()) timer.start(freq);
00514     kDebug(7001) << "Global Poll Freq is now" << freq << "msec";
00515   }
00516 }
00517 
00518 
00519 #if defined(HAVE_FAM)
00520 // setup FAM notification, returns false if not possible
00521 bool KDirWatchPrivate::useFAM(Entry* e)
00522 {
00523   if (!use_fam) return false;
00524 
00525   // handle FAM events to avoid deadlock
00526   // (FAM sends back all files in a directory when monitoring)
00527   famEventReceived();
00528 
00529   e->m_mode = FAMMode;
00530   e->dirty = false;
00531 
00532   if (e->isDir) {
00533     if (e->m_status == NonExistent) {
00534       // If the directory does not exist we watch the parent directory
00535       addEntry(0, QDir::cleanPath(e->path+"/.."), e, true);
00536     }
00537     else {
00538       int res =FAMMonitorDirectory(&fc, QFile::encodeName(e->path),
00539                    &(e->fr), e);
00540       if (res<0) {
00541     e->m_mode = UnknownMode;
00542     use_fam=false;
00543         delete sn; sn = 0;
00544     return false;
00545       }
00546       kDebug(7001).nospace() << " Setup FAM (Req " << FAMREQUEST_GETREQNUM(&(e->fr))
00547                    << ") for " << e->path;
00548     }
00549   }
00550   else {
00551     if (e->m_status == NonExistent) {
00552       // If the file does not exist we watch the directory
00553       addEntry(0, QFileInfo(e->path).absolutePath(), e, true);
00554     }
00555     else {
00556       int res = FAMMonitorFile(&fc, QFile::encodeName(e->path),
00557                    &(e->fr), e);
00558       if (res<0) {
00559     e->m_mode = UnknownMode;
00560     use_fam=false;
00561         delete sn; sn = 0;
00562     return false;
00563       }
00564 
00565       kDebug(7001).nospace() << " Setup FAM (Req " << FAMREQUEST_GETREQNUM(&(e->fr))
00566                    << ") for " << e->path;
00567     }
00568   }
00569 
00570   // handle FAM events to avoid deadlock
00571   // (FAM sends back all files in a directory when monitoring)
00572   famEventReceived();
00573 
00574   return true;
00575 }
00576 #endif
00577 
00578 #ifdef HAVE_SYS_INOTIFY_H
00579 // setup INotify notification, returns false if not possible
00580 bool KDirWatchPrivate::useINotify( Entry* e )
00581 {
00582   //kDebug (7001) << "trying to use inotify for monitoring";
00583 
00584   e->wd = 0;
00585   e->dirty = false;
00586 
00587   if (!supports_inotify) return false;
00588 
00589   e->m_mode = INotifyMode;
00590 
00591   if ( e->m_status == NonExistent ) {
00592     addEntry(0, QDir::cleanPath(e->path+"/.."), e, true);
00593     return true;
00594   }
00595 
00596   // May as well register for almost everything - it's free!
00597   int mask = IN_DELETE|IN_DELETE_SELF|IN_CREATE|IN_MOVE|IN_MOVE_SELF|IN_DONT_FOLLOW|IN_MOVED_FROM|IN_MODIFY|IN_ATTRIB;
00598 
00599   if ( ( e->wd = inotify_add_watch( m_inotify_fd,
00600                                     QFile::encodeName( e->path ), mask) ) > 0)
00601   {
00602     //kDebug (7001) << "inotify successfully used for monitoring";
00603     return true;
00604   }
00605 
00606   return false;
00607 }
00608 #endif
00609 #ifdef HAVE_QFILESYSTEMWATCHER
00610 bool KDirWatchPrivate::useQFSWatch(Entry* e)
00611 {
00612   e->m_mode = QFSWatchMode;
00613   e->dirty = false;
00614 
00615   if ( e->m_status == NonExistent ) {
00616     addEntry( 0, QDir::cleanPath( e->path + "/.." ), e, true );
00617     return true;
00618   }
00619 
00620   fsWatcher->addPath( e->path );
00621   return true;
00622 }
00623 #endif
00624 
00625 bool KDirWatchPrivate::useStat(Entry* e)
00626 {
00627   KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByPath(e->path);
00628   const bool slow = mp ? mp->probablySlow() : false;
00629   if (slow)
00630     useFreq(e, m_nfsPollInterval);
00631   else
00632     useFreq(e, m_PollInterval);
00633 
00634   if (e->m_mode != StatMode) {
00635     e->m_mode = StatMode;
00636     statEntries++;
00637 
00638     if ( statEntries == 1 ) {
00639       // if this was first STAT entry (=timer was stopped)
00640       timer.start(freq);      // then start the timer
00641       kDebug(7001) << " Started Polling Timer, freq " << freq;
00642     }
00643   }
00644 
00645   kDebug(7001) << " Setup Stat (freq " << e->freq << ") for " << e->path;
00646 
00647   return true;
00648 }
00649 
00650 
00651 /* If <instance> !=0, this KDirWatch instance wants to watch at <_path>,
00652  * providing in <isDir> the type of the entry to be watched.
00653  * Sometimes, entries are dependant on each other: if <sub_entry> !=0,
00654  * this entry needs another entry to watch himself (when notExistent).
00655  */
00656 void KDirWatchPrivate::addEntry(KDirWatch* instance, const QString& _path,
00657                 Entry* sub_entry, bool isDir, KDirWatch::WatchModes watchModes)
00658 {
00659   QString path (_path);
00660   if (path.isEmpty()
00661 #ifndef Q_WS_WIN
00662     || path.startsWith("/dev/") || (path == "/dev")
00663 #endif
00664   )
00665     return; // Don't even go there.
00666 
00667   if ( path.length() > 1 && path.endsWith( QLatin1Char( '/' ) ) )
00668     path.truncate( path.length() - 1 );
00669 
00670   EntryMap::Iterator it = m_mapEntries.find( path );
00671   if ( it != m_mapEntries.end() )
00672   {
00673     if (sub_entry) {
00674        (*it).m_entries.append(sub_entry);
00675        kDebug(7001) << "Added already watched Entry" << path
00676                     << "(for" << sub_entry->path << ")";
00677 #ifdef HAVE_SYS_INOTIFY_H
00678        Entry* e = &(*it);
00679        if( (e->m_mode == INotifyMode) && (e->wd > 0) ) {
00680          int mask = IN_DELETE|IN_DELETE_SELF|IN_CREATE|IN_MOVE|IN_MOVE_SELF|IN_DONT_FOLLOW;
00681          if(!e->isDir)
00682            mask |= IN_MODIFY|IN_ATTRIB;
00683          else
00684            mask |= IN_ONLYDIR;
00685 
00686          inotify_rm_watch (m_inotify_fd, e->wd);
00687          e->wd = inotify_add_watch( m_inotify_fd, QFile::encodeName( e->path ),
00688                                     mask);
00689        }
00690 #endif
00691     }
00692     else {
00693        (*it).addClient(instance, watchModes);
00694        kDebug(7001) << "Added already watched Entry" << path
00695              << "(now" <<  (*it).clients() << "clients)"
00696              << QString("[%1]").arg(instance->objectName());
00697     }
00698     return;
00699   }
00700 
00701   // we have a new path to watch
00702 
00703   KDE_struct_stat stat_buf;
00704   bool exists = (KDE::stat(path, &stat_buf) == 0);
00705 
00706   EntryMap::iterator newIt = m_mapEntries.insert( path, Entry() );
00707   // the insert does a copy, so we have to use <e> now
00708   Entry* e = &(*newIt);
00709 
00710   if (exists) {
00711     e->isDir = S_ISDIR(stat_buf.st_mode);
00712 
00713     if (e->isDir && !isDir) {
00714       KDE::lstat(path, &stat_buf);
00715       if (S_ISLNK(stat_buf.st_mode))
00716         // if it's a symlink, don't follow it
00717         e->isDir = false;
00718       else
00719         qWarning() << "KDirWatch:" << path << "is a directory. Use addDir!";
00720     } else if (!e->isDir && isDir)
00721       qWarning("KDirWatch: %s is a file. Use addFile!", qPrintable(path));
00722 
00723     if (!e->isDir && ( watchModes != KDirWatch::WatchDirOnly)) {
00724       qWarning() << "KDirWatch:" << path << "is a file. You can't use recursive or "
00725                     "watchFiles options";
00726       watchModes = KDirWatch::WatchDirOnly;
00727     }
00728 
00729 #ifdef Q_OS_WIN
00730     // ctime is the 'creation time' on windows - use mtime instead
00731     e->m_ctime = stat_buf.st_mtime;
00732 #else
00733     e->m_ctime = stat_buf.st_ctime;
00734 #endif
00735     e->m_status = Normal;
00736     e->m_nlink = stat_buf.st_nlink;
00737   }
00738   else {
00739     e->isDir = isDir;
00740     e->m_ctime = invalid_ctime;
00741     e->m_status = NonExistent;
00742     e->m_nlink = 0;
00743   }
00744 
00745   e->path = path;
00746   if (sub_entry)
00747     e->m_entries.append(sub_entry);
00748   else
00749     e->addClient(instance, watchModes);
00750 
00751   kDebug(7001).nospace() << "Added " << (e->isDir ? "Dir " : "File ") << path
00752     << (e->m_status == NonExistent ? " NotExisting" : "")
00753     << " for " << (sub_entry ? sub_entry->path : "")
00754     << " [" << (instance ? instance->objectName() : "") << "]";
00755 
00756   // now setup the notification method
00757   e->m_mode = UnknownMode;
00758   e->msecLeft = 0;
00759 
00760   if ( isNoisyFile( QFile::encodeName( path ) ) )
00761     return;
00762 
00763   if (exists && e->isDir && (watchModes != KDirWatch::WatchDirOnly)) {
00764     QFlags<QDir::Filter> filters = QDir::NoDotAndDotDot;
00765 
00766     if ((watchModes & KDirWatch::WatchSubDirs) &&
00767         (watchModes & KDirWatch::WatchFiles)) {
00768       filters |= (QDir::Dirs|QDir::Files);
00769     } else if (watchModes & KDirWatch::WatchSubDirs) {
00770       filters |= QDir::Dirs;
00771     } else if (watchModes & KDirWatch::WatchFiles) {
00772       filters |= QDir::Files;
00773     }
00774 
00775 #if defined(HAVE_SYS_INOTIFY_H)
00776     if (e->m_mode == INotifyMode || (e->m_mode == UnknownMode && m_preferredMethod == INotify)  )
00777     {
00778         kDebug (7001) << "Ignoring WatchFiles directive - this is implicit with inotify";
00779         // Placing a watch on individual files is redundant with inotify
00780         // (inotify gives us WatchFiles functionality "for free") and indeed
00781         // actively harmful, so prevent it.  WatchSubDirs is necessary, though.
00782         filters &= ~QDir::Files;
00783     }
00784 #endif
00785 
00786     QDir basedir (e->path);
00787     const QFileInfoList contents = basedir.entryInfoList(filters);
00788     for (QFileInfoList::const_iterator iter = contents.constBegin();
00789          iter != contents.constEnd(); ++iter)
00790     {
00791       const QFileInfo &fileInfo = *iter;
00792       // treat symlinks as files--don't follow them.
00793       bool isDir = fileInfo.isDir() && !fileInfo.isSymLink();
00794 
00795       addEntry (instance, fileInfo.absoluteFilePath(), 0, isDir,
00796                 isDir ? watchModes : KDirWatch::WatchDirOnly);
00797     }
00798   }
00799 
00800   // If the watch is on a network filesystem use the nfsPreferredMethod as the
00801   // default, otherwise use preferredMethod as the default, if the methods are
00802   // the same we can skip the mountpoint check
00803 
00804   // This allows to configure a different method for NFS mounts, since inotify
00805   // cannot detect changes made by other machines. However as a default inotify
00806   // is fine, since the most common case is a NFS-mounted home, where all changes
00807   // are made locally. #177892.
00808   WatchMethod preferredMethod = m_preferredMethod;
00809   if (m_nfsPreferredMethod != m_preferredMethod) {
00810     KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByPath(e->path);
00811     if (mountPoint && mountPoint->probablySlow()) {
00812       preferredMethod = m_nfsPreferredMethod;
00813     }
00814   }
00815 
00816   // Try the appropriate preferred method from the config first
00817   bool entryAdded = false;
00818   switch (preferredMethod) {
00819 #if defined(HAVE_FAM)
00820   case Fam: entryAdded = useFAM(e); break;
00821 #endif
00822 #if defined(HAVE_SYS_INOTIFY_H)
00823   case INotify: entryAdded = useINotify(e); break;
00824 #endif
00825 #if defined(HAVE_QFILESYSTEMWATCHER)
00826   case QFSWatch: entryAdded = useQFSWatch(e); break;
00827 #endif
00828   case Stat: entryAdded = useStat(e); break;
00829   default: break;
00830   }
00831 
00832   // Failing that try in order INotify, FAM, QFSWatch, Stat
00833   if (!entryAdded) {
00834 #if defined(HAVE_SYS_INOTIFY_H)
00835     if (useINotify(e)) return;
00836 #endif
00837 #if defined(HAVE_FAM)
00838     if (useFAM(e)) return;
00839 #endif
00840 #if defined(HAVE_QFILESYSTEMWATCHER)
00841     if (useQFSWatch(e)) return;
00842 #endif
00843     useStat(e);
00844   }
00845 }
00846 
00847 
00848 void KDirWatchPrivate::removeEntry(KDirWatch* instance,
00849                                    const QString& _path,
00850                                    Entry* sub_entry)
00851 {
00852   //kDebug(7001) << "path=" << _path << "sub_entry:" << sub_entry;
00853   Entry* e = entry(_path);
00854   if (!e) {
00855     kWarning(7001) << "doesn't know" << _path;
00856     return;
00857   }
00858 
00859   removeEntry(instance, e, sub_entry);
00860 }
00861 
00862 void KDirWatchPrivate::removeEntry(KDirWatch* instance,
00863                                    Entry* e,
00864                                    Entry* sub_entry)
00865 {
00866   removeList.remove(e);
00867 
00868   if (sub_entry)
00869     e->m_entries.removeAll(sub_entry);
00870   else
00871     e->removeClient(instance);
00872 
00873   if (e->m_clients.count() || e->m_entries.count())
00874     return;
00875 
00876   if (delayRemove) {
00877     removeList.insert(e);
00878     // now e->isValid() is false
00879     return;
00880   }
00881 
00882 #ifdef HAVE_FAM
00883   if (e->m_mode == FAMMode) {
00884     if ( e->m_status == Normal) {
00885       FAMCancelMonitor(&fc, &(e->fr) );
00886       kDebug(7001).nospace()  << "Cancelled FAM (Req " << FAMREQUEST_GETREQNUM(&(e->fr))
00887                     << ") for " << e->path;
00888     }
00889     else {
00890       if (e->isDir)
00891     removeEntry(0, QDir::cleanPath(e->path+"/.."), e);
00892       else
00893     removeEntry(0, QFileInfo(e->path).absolutePath(), e);
00894     }
00895   }
00896 #endif
00897 
00898 #ifdef HAVE_SYS_INOTIFY_H
00899   if (e->m_mode == INotifyMode) {
00900     if ( e->m_status == Normal ) {
00901       (void) inotify_rm_watch( m_inotify_fd, e->wd );
00902       kDebug(7001).nospace() << "Cancelled INotify (fd " << m_inotify_fd << ", "
00903                              << e->wd << ") for " << e->path;
00904     }
00905     else {
00906       if (e->isDir)
00907         removeEntry(0, QDir::cleanPath(e->path+"/.."), e);
00908       else
00909         removeEntry(0, QFileInfo(e->path).absolutePath(), e);
00910     }
00911   }
00912 #endif
00913 
00914 #ifdef HAVE_QFILESYSTEMWATCHER
00915   if (e->m_mode == QFSWatchMode) {
00916     fsWatcher->removePath(e->path);
00917   }
00918 #endif
00919   if (e->m_mode == StatMode) {
00920     statEntries--;
00921     if ( statEntries == 0 ) {
00922       timer.stop(); // stop timer if lists are empty
00923       kDebug(7001) << " Stopped Polling Timer";
00924     }
00925   }
00926 
00927   //kDebug(7001).nospace() << "Removed " << (e->isDir ? "Dir ":"File ") << e->path
00928   //   << " for " << (sub_entry ? sub_entry->path : "")
00929   //   << " [" << (instance ? instance->objectName() : "") << "]";
00930   m_mapEntries.remove( e->path ); // <e> not valid any more
00931 }
00932 
00933 
00934 /* Called from KDirWatch destructor:
00935  * remove <instance> as client from all entries
00936  */
00937 void KDirWatchPrivate::removeEntries( KDirWatch* instance )
00938 {
00939   int minfreq = 3600000;
00940 
00941   QStringList pathList;
00942   // put all entries where instance is a client in list
00943   EntryMap::Iterator it = m_mapEntries.begin();
00944   for( ; it != m_mapEntries.end(); ++it ) {
00945     Client* c = 0;
00946     foreach(Client* client, (*it).m_clients) {
00947       if (client->instance == instance) {
00948         c = client;
00949         break;
00950       }
00951     }
00952     if (c) {
00953       c->count = 1; // forces deletion of instance as client
00954       pathList.append((*it).path);
00955     }
00956     else if ( (*it).m_mode == StatMode && (*it).freq < minfreq )
00957       minfreq = (*it).freq;
00958   }
00959 
00960   foreach(const QString &path, pathList)
00961     removeEntry(instance, path, 0);
00962 
00963   if (minfreq > freq) {
00964     // we can decrease the global polling frequency
00965     freq = minfreq;
00966     if (timer.isActive()) timer.start(freq);
00967     kDebug(7001) << "Poll Freq now" << freq << "msec";
00968   }
00969 }
00970 
00971 // instance ==0: stop scanning for all instances
00972 bool KDirWatchPrivate::stopEntryScan( KDirWatch* instance, Entry* e)
00973 {
00974   int stillWatching = 0;
00975   foreach(Client* client, e->m_clients) {
00976     if (!instance || instance == client->instance)
00977       client->watchingStopped = true;
00978     else if (!client->watchingStopped)
00979       stillWatching += client->count;
00980   }
00981 
00982   kDebug(7001)  << (instance ? instance->objectName() : "all")
00983                 << "stopped scanning" << e->path << "(now"
00984                 << stillWatching << "watchers)";
00985 
00986   if (stillWatching == 0) {
00987     // if nobody is interested, we don't watch
00988     if ( e->m_mode != INotifyMode ) {
00989       e->m_ctime = invalid_ctime; // invalid
00990       e->m_status = NonExistent;
00991     }
00992     //    e->m_status = Normal;
00993   }
00994   return true;
00995 }
00996 
00997 // instance ==0: start scanning for all instances
00998 bool KDirWatchPrivate::restartEntryScan( KDirWatch* instance, Entry* e,
00999                      bool notify)
01000 {
01001   int wasWatching = 0, newWatching = 0;
01002   foreach(Client* client, e->m_clients) {
01003     if (!client->watchingStopped)
01004       wasWatching += client->count;
01005     else if (!instance || instance == client->instance) {
01006       client->watchingStopped = false;
01007       newWatching += client->count;
01008     }
01009   }
01010   if (newWatching == 0)
01011     return false;
01012 
01013   kDebug(7001)  << (instance ? instance->objectName() : "all")
01014                 << "restarted scanning" << e->path
01015                 << "(now" << wasWatching+newWatching << "watchers)";
01016 
01017   // restart watching and emit pending events
01018 
01019   int ev = NoChange;
01020   if (wasWatching == 0) {
01021     if (!notify) {
01022       KDE_struct_stat stat_buf;
01023       bool exists = (KDE::stat(e->path, &stat_buf) == 0);
01024       if (exists) {
01025 #ifdef Q_OS_WIN
01026         // ctime is the 'creation time' on windows - use mtime instead
01027         e->m_ctime = stat_buf.st_mtime;
01028 #else
01029         e->m_ctime = stat_buf.st_ctime;
01030 #endif
01031         e->m_status = Normal;
01032         e->m_nlink = stat_buf.st_nlink;
01033       }
01034       else {
01035         e->m_ctime = invalid_ctime;
01036         e->m_status = NonExistent;
01037         e->m_nlink = 0;
01038       }
01039     }
01040     e->msecLeft = 0;
01041     ev = scanEntry(e);
01042   }
01043   emitEvent(e,ev);
01044 
01045   return true;
01046 }
01047 
01048 // instance ==0: stop scanning for all instances
01049 void KDirWatchPrivate::stopScan(KDirWatch* instance)
01050 {
01051   EntryMap::Iterator it = m_mapEntries.begin();
01052   for( ; it != m_mapEntries.end(); ++it )
01053     stopEntryScan(instance, &(*it));
01054 }
01055 
01056 
01057 void KDirWatchPrivate::startScan(KDirWatch* instance,
01058                                  bool notify, bool skippedToo )
01059 {
01060   if (!notify)
01061     resetList(instance,skippedToo);
01062 
01063   EntryMap::Iterator it = m_mapEntries.begin();
01064   for( ; it != m_mapEntries.end(); ++it )
01065     restartEntryScan(instance, &(*it), notify);
01066 
01067   // timer should still be running when in polling mode
01068 }
01069 
01070 
01071 // clear all pending events, also from stopped
01072 void KDirWatchPrivate::resetList( KDirWatch* /*instance*/, bool skippedToo )
01073 {
01074   EntryMap::Iterator it = m_mapEntries.begin();
01075   for( ; it != m_mapEntries.end(); ++it ) {
01076 
01077     foreach(Client* client, (*it).m_clients) {
01078       if (!client->watchingStopped || skippedToo)
01079         client->pending = NoChange;
01080     }
01081   }
01082 }
01083 
01084 // Return event happened on <e>
01085 //
01086 int KDirWatchPrivate::scanEntry(Entry* e)
01087 {
01088 #ifdef HAVE_FAM
01089   if (e->m_mode == FAMMode) {
01090     // we know nothing has changed, no need to stat
01091     if(!e->dirty) return NoChange;
01092     e->dirty = false;
01093   }
01094 #endif
01095 
01096   // Shouldn't happen: Ignore "unknown" notification method
01097   if (e->m_mode == UnknownMode) return NoChange;
01098 
01099 #if defined( HAVE_SYS_INOTIFY_H )
01100   if (e->m_mode == DNotifyMode || e->m_mode == INotifyMode ) {
01101     // we know nothing has changed, no need to stat
01102     if(!e->dirty) return NoChange;
01103     e->dirty = false;
01104   }
01105 #endif
01106 
01107 #if defined( HAVE_QFILESYSTEMWATCHER )
01108   if (e->m_mode == QFSWatchMode ) {
01109     // we know nothing has changed, no need to stat
01110     if(!e->dirty) return NoChange;
01111     e->dirty = false;
01112   }
01113 #endif
01114 
01115   if (e->m_mode == StatMode) {
01116     // only scan if timeout on entry timer happens;
01117     // e.g. when using 500msec global timer, a entry
01118     // with freq=5000 is only watched every 10th time
01119 
01120     e->msecLeft -= freq;
01121     if (e->msecLeft>0) return NoChange;
01122     e->msecLeft += e->freq;
01123   }
01124 
01125   KDE_struct_stat stat_buf;
01126   bool exists = (KDE::stat(e->path, &stat_buf) == 0);
01127   if (exists) {
01128 
01129     if (e->m_status == NonExistent) {
01130 #ifdef Q_OS_WIN
01131       // ctime is the 'creation time' on windows - use mtime instead
01132       e->m_ctime = stat_buf.st_mtime;
01133 #else
01134       e->m_ctime = stat_buf.st_ctime;
01135 #endif
01136       e->m_status = Normal;
01137       e->m_nlink = stat_buf.st_nlink;
01138       return Created;
01139     }
01140 
01141 #ifdef Q_OS_WIN
01142     stat_buf.st_ctime = stat_buf.st_mtime;
01143 #endif
01144     if ( (e->m_ctime != invalid_ctime) &&
01145           ((stat_buf.st_ctime != e->m_ctime) ||
01146           (stat_buf.st_nlink != (nlink_t) e->m_nlink))
01147 #if defined( HAVE_QFILESYSTEMWATCHER )
01148           // we trust QFSW to get it right, the ctime comparisons above
01149           // fail for example when adding files to directories on Windows
01150           // which doesn't change the mtime of the directory
01151         ||(e->m_mode == QFSWatchMode )
01152 #endif
01153     ) {
01154       e->m_ctime = stat_buf.st_ctime;
01155       e->m_nlink = stat_buf.st_nlink;
01156       return Changed;
01157     }
01158 
01159     return NoChange;
01160   }
01161 
01162   // dir/file doesn't exist
01163 
01164   if (e->m_ctime == invalid_ctime) {
01165     e->m_nlink = 0;
01166     e->m_status = NonExistent;
01167     return NoChange;
01168   }
01169 
01170   e->m_ctime = invalid_ctime;
01171   e->m_nlink = 0;
01172   e->m_status = NonExistent;
01173 
01174   return Deleted;
01175 }
01176 
01177 /* Notify all interested KDirWatch instances about a given event on an entry
01178  * and stored pending events. When watching is stopped, the event is
01179  * added to the pending events.
01180  */
01181 void KDirWatchPrivate::emitEvent(const Entry* e, int event, const QString &fileName)
01182 {
01183   QString path (e->path);
01184   if (!fileName.isEmpty()) {
01185     if (!QDir::isRelativePath(fileName))
01186       path = fileName;
01187     else
01188 #ifdef Q_OS_UNIX
01189       path += '/' + fileName;
01190 #elif defined(Q_WS_WIN)
01191       //current drive is passed instead of /
01192       path += QDir::currentPath().left(2) + '/' + fileName;
01193 #endif
01194   }
01195 
01196   foreach(Client* c, e->m_clients)
01197   {
01198     if (c->instance==0 || c->count==0) continue;
01199 
01200     if (c->watchingStopped) {
01201       // add event to pending...
01202       if (event == Changed)
01203         c->pending |= event;
01204       else if (event == Created || event == Deleted)
01205         c->pending = event;
01206       continue;
01207     }
01208     // not stopped
01209     if (event == NoChange || event == Changed)
01210       event |= c->pending;
01211     c->pending = NoChange;
01212     if (event == NoChange) continue;
01213 
01214     if (event & Deleted) {
01215       c->instance->setDeleted(path);
01216       // emit only Deleted event...
01217       continue;
01218     }
01219 
01220     if (event & Created) {
01221       c->instance->setCreated(path);
01222       // possible emit Change event after creation
01223     }
01224 
01225     if (event & Changed)
01226       c->instance->setDirty(path);
01227   }
01228 }
01229 
01230 // Remove entries which were marked to be removed
01231 void KDirWatchPrivate::slotRemoveDelayed()
01232 {
01233   delayRemove = false;
01234   // Removing an entry could also take care of removing its parent
01235   // (e.g. in FAM or inotify mode), which would remove other entries in removeList,
01236   // so don't use foreach or iterators here...
01237   while (!removeList.isEmpty()) {
01238     Entry* entry = *removeList.begin();
01239     removeEntry(0, entry, 0); // this will remove entry from removeList
01240   }
01241 }
01242 
01243 /* Scan all entries to be watched for changes. This is done regularly
01244  * when polling. This is NOT used by FAM.
01245  */
01246 void KDirWatchPrivate::slotRescan()
01247 {
01248   EntryMap::Iterator it;
01249 
01250   // People can do very long things in the slot connected to dirty(),
01251   // like showing a message box. We don't want to keep polling during
01252   // that time, otherwise the value of 'delayRemove' will be reset.
01253   bool timerRunning = timer.isActive();
01254   if ( timerRunning )
01255     timer.stop();
01256 
01257   // We delay deletions of entries this way.
01258   // removeDir(), when called in slotDirty(), can cause a crash otherwise
01259   delayRemove = true;
01260 
01261   if (rescan_all)
01262   {
01263     // mark all as dirty
01264     it = m_mapEntries.begin();
01265     for( ; it != m_mapEntries.end(); ++it )
01266       (*it).dirty = true;
01267     rescan_all = false;
01268   }
01269   else
01270   {
01271     // progate dirty flag to dependant entries (e.g. file watches)
01272     it = m_mapEntries.begin();
01273     for( ; it != m_mapEntries.end(); ++it )
01274       if (((*it).m_mode == INotifyMode || (*it).m_mode == DNotifyMode) && (*it).dirty )
01275         (*it).propagate_dirty();
01276   }
01277 
01278 #ifdef HAVE_SYS_INOTIFY_H
01279   QList<Entry*> dList, cList;
01280 #endif
01281 
01282   it = m_mapEntries.begin();
01283   for( ; it != m_mapEntries.end(); ++it ) {
01284     // we don't check invalid entries (i.e. remove delayed)
01285     if (!(*it).isValid()) continue;
01286 
01287     int ev = scanEntry( &(*it) );
01288 
01289 #ifdef HAVE_SYS_INOTIFY_H
01290     if ((*it).m_mode == INotifyMode) {
01291       if ( ev == Deleted ) {
01292         addEntry(0, QDir::cleanPath( ( *it ).path+"/.."), &*it, true);
01293       }
01294     }
01295     if ((*it).m_mode == INotifyMode && ev == Created && (*it).wd == 0) {
01296       cList.append( &(*it) );
01297       if (! useINotify( &(*it) )) {
01298         useStat( &(*it) );
01299       }
01300     }
01301 
01302     if ((*it).isDir)
01303     {
01304       // Report and clear the the list of files that have changed in this directory.
01305       // Remove duplicates by changing to set and back again:
01306       // we don't really care about preserving the order of the
01307       // original changes.
01308       QList<QString> pendingFileChanges = (*it).m_pendingFileChanges.toSet().toList();
01309       Q_FOREACH(QString changedFilename, pendingFileChanges )
01310       {
01311         emitEvent(&(*it), Changed, changedFilename);
01312       }
01313       (*it).m_pendingFileChanges.clear();
01314     }
01315 #endif
01316 
01317     if ( ev != NoChange )
01318       emitEvent( &(*it), ev);
01319   }
01320 
01321   if ( timerRunning )
01322     timer.start(freq);
01323 
01324 #ifdef HAVE_SYS_INOTIFY_H
01325   // Remove watch of parent of new created directories
01326   Q_FOREACH(Entry* e, cList)
01327     removeEntry(0, QDir::cleanPath( e->path+"/.."), e);
01328 #endif
01329 
01330   QTimer::singleShot(0, this, SLOT(slotRemoveDelayed()));
01331 }
01332 
01333 bool KDirWatchPrivate::isNoisyFile( const char * filename )
01334 {
01335   // $HOME/.X.err grows with debug output, so don't notify change
01336   if ( *filename == '.') {
01337     if (strncmp(filename, ".X.err", 6) == 0) return true;
01338     if (strncmp(filename, ".xsession-errors", 16) == 0) return true;
01339     // fontconfig updates the cache on every KDE app start
01340     // (inclusive kio_thumbnail slaves)
01341     if (strncmp(filename, ".fonts.cache", 12) == 0) return true;
01342   }
01343 
01344   return false;
01345 }
01346 
01347 #ifdef HAVE_FAM
01348 void KDirWatchPrivate::famEventReceived()
01349 {
01350   static FAMEvent fe;
01351 
01352   delayRemove = true;
01353 
01354   //kDebug(7001) << "Fam event received";
01355 
01356   while(use_fam && FAMPending(&fc)) {
01357     if (FAMNextEvent(&fc, &fe) == -1) {
01358       kWarning(7001) << "FAM connection problem, switching to polling.";
01359       use_fam = false;
01360       delete sn; sn = 0;
01361 
01362       // Replace all FAMMode entries with DNotify/Stat
01363       EntryMap::Iterator it;
01364       it = m_mapEntries.begin();
01365       for( ; it != m_mapEntries.end(); ++it )
01366         if ((*it).m_mode == FAMMode && (*it).m_clients.count()>0) {
01367 #ifdef HAVE_SYS_INOTIFY_H
01368           if (useINotify( &(*it) )) continue;
01369 #endif
01370           useStat( &(*it) );
01371         }
01372     }
01373     else
01374       checkFAMEvent(&fe);
01375   }
01376 
01377   QTimer::singleShot(0, this, SLOT(slotRemoveDelayed()));
01378 }
01379 
01380 void KDirWatchPrivate::checkFAMEvent(FAMEvent* fe)
01381 {
01382   //kDebug(7001);
01383 
01384   // Don't be too verbose ;-)
01385   if ((fe->code == FAMExists) ||
01386       (fe->code == FAMEndExist) ||
01387       (fe->code == FAMAcknowledge)) return;
01388 
01389   if ( isNoisyFile( fe->filename ) )
01390     return;
01391 
01392   Entry* e = 0;
01393   EntryMap::Iterator it = m_mapEntries.begin();
01394   for( ; it != m_mapEntries.end(); ++it )
01395     if (FAMREQUEST_GETREQNUM(&( (*it).fr )) ==
01396        FAMREQUEST_GETREQNUM(&(fe->fr)) ) {
01397       e = &(*it);
01398       break;
01399     }
01400 
01401   // Entry* e = static_cast<Entry*>(fe->userdata);
01402 
01403 #if 0 // #88538
01404   kDebug(7001)  << "Processing FAM event ("
01405                 << ((fe->code == FAMChanged) ? "FAMChanged" :
01406                     (fe->code == FAMDeleted) ? "FAMDeleted" :
01407                     (fe->code == FAMStartExecuting) ? "FAMStartExecuting" :
01408                     (fe->code == FAMStopExecuting) ? "FAMStopExecuting" :
01409                     (fe->code == FAMCreated) ? "FAMCreated" :
01410                     (fe->code == FAMMoved) ? "FAMMoved" :
01411                     (fe->code == FAMAcknowledge) ? "FAMAcknowledge" :
01412                     (fe->code == FAMExists) ? "FAMExists" :
01413                     (fe->code == FAMEndExist) ? "FAMEndExist" : "Unknown Code")
01414                 << ", " << fe->filename
01415                 << ", Req " << FAMREQUEST_GETREQNUM(&(fe->fr)) << ")";
01416 #endif
01417 
01418   if (!e) {
01419     // this happens e.g. for FAMAcknowledge after deleting a dir...
01420     //    kDebug(7001) << "No entry for FAM event ?!";
01421     return;
01422   }
01423 
01424   if (e->m_status == NonExistent) {
01425     kDebug(7001) << "FAM event for nonExistent entry " << e->path;
01426     return;
01427   }
01428 
01429   // Delayed handling. This rechecks changes with own stat calls.
01430   e->dirty = true;
01431   if (!rescan_timer.isActive())
01432     rescan_timer.start(m_PollInterval); // singleshot
01433 
01434   // needed FAM control actions on FAM events
01435   if (e->isDir)
01436     switch (fe->code)
01437     {
01438       case FAMDeleted:
01439        // file absolute: watched dir
01440         if (!QDir::isRelativePath(fe->filename))
01441         {
01442           // a watched directory was deleted
01443 
01444           e->m_status = NonExistent;
01445           FAMCancelMonitor(&fc, &(e->fr) ); // needed ?
01446           kDebug(7001)  << "Cancelled FAMReq"
01447                         << FAMREQUEST_GETREQNUM(&(e->fr))
01448                         << "for" << e->path;
01449           // Scan parent for a new creation
01450           addEntry(0, QDir::cleanPath( e->path+"/.."), e, true);
01451         }
01452         break;
01453 
01454       case FAMCreated: {
01455           // check for creation of a directory we have to watch
01456         QString tpath(e->path + QLatin1Char('/') + fe->filename);
01457 
01458         Entry* sub_entry = e->findSubEntry(tpath);
01459         if (sub_entry && sub_entry->isDir) {
01460           removeEntry(0, e, sub_entry);
01461           sub_entry->m_status = Normal;
01462           if (!useFAM(sub_entry)) {
01463 #ifdef HAVE_SYS_INOTIFY_H
01464             if (!useINotify(sub_entry ))
01465 #endif
01466               useStat(sub_entry);
01467           }
01468         }
01469         else if ((sub_entry == 0) && (!e->m_clients.empty())) {
01470           bool isDir = false;
01471           const QList<Client *> clients = e->clientsForFileOrDir(tpath, &isDir);
01472           Q_FOREACH(Client *client, clients) {
01473             addEntry (client->instance, tpath, 0, isDir,
01474                       isDir ? client->m_watchModes : KDirWatch::WatchDirOnly);
01475           }
01476 
01477           if (!clients.isEmpty()) {
01478             emitEvent(e, Created, tpath);
01479 
01480             QString msg (QString::number(clients.count()));
01481             msg += " instance/s monitoring the new ";
01482             msg += (isDir ? "dir " : "file ") + tpath;
01483             kDebug(7001) << msg;
01484           }
01485         }
01486       }
01487         break;
01488       default:
01489         break;
01490     }
01491 }
01492 #else
01493 void KDirWatchPrivate::famEventReceived()
01494 {
01495     kWarning (7001) << "Fam event received but FAM is not supported";
01496 }
01497 #endif
01498 
01499 
01500 void KDirWatchPrivate::statistics()
01501 {
01502   EntryMap::Iterator it;
01503 
01504   kDebug(7001) << "Entries watched:";
01505   if (m_mapEntries.count()==0) {
01506     kDebug(7001) << "  None.";
01507   }
01508   else {
01509     it = m_mapEntries.begin();
01510     for( ; it != m_mapEntries.end(); ++it ) {
01511       Entry* e = &(*it);
01512       kDebug(7001)  << "  " << e->path << " ("
01513                     << ((e->m_status==Normal)?"":"Nonexistent ")
01514                     << (e->isDir ? "Dir":"File") << ", using "
01515                     << ((e->m_mode == FAMMode) ? "FAM" :
01516                         (e->m_mode == INotifyMode) ? "INotify" :
01517                         (e->m_mode == DNotifyMode) ? "DNotify" :
01518                         (e->m_mode == QFSWatchMode) ? "QFSWatch" :
01519                         (e->m_mode == StatMode) ? "Stat" : "Unknown Method")
01520                     << ")";
01521 
01522       foreach(Client* c, e->m_clients) {
01523         QString pending;
01524         if (c->watchingStopped) {
01525           if (c->pending & Deleted) pending += "deleted ";
01526           if (c->pending & Created) pending += "created ";
01527           if (c->pending & Changed) pending += "changed ";
01528           if (!pending.isEmpty()) pending = " (pending: " + pending + ')';
01529           pending = ", stopped" + pending;
01530         }
01531         kDebug(7001)  << "    by " << c->instance->objectName()
01532                       << " (" << c->count << " times)" << pending;
01533       }
01534       if (e->m_entries.count()>0) {
01535         kDebug(7001) << "    dependent entries:";
01536         foreach(Entry *d, e->m_entries) {
01537           kDebug(7001) << "      " << d->path;
01538         }
01539       }
01540     }
01541   }
01542 }
01543 
01544 #ifdef HAVE_QFILESYSTEMWATCHER
01545 // Slot for QFileSystemWatcher
01546 void KDirWatchPrivate::fswEventReceived(const QString &path)
01547 {
01548   EntryMap::Iterator it;
01549   it = m_mapEntries.find(path);
01550   if(it != m_mapEntries.end()) {
01551     Entry entry = *it;  // deep copy to not point to uninialized data (can happen inside emitEvent() )
01552     Entry *e = &entry;
01553     e->dirty = true;
01554     int ev = scanEntry(e);
01555     if (ev != NoChange)
01556       emitEvent(e, ev);
01557     if(ev == Deleted) {
01558       if (e->isDir)
01559         addEntry(0, QDir::cleanPath(e->path + "/.."), e, true);
01560       else
01561         addEntry(0, QFileInfo(e->path).absolutePath(), e, true);
01562     } else
01563     if (ev == Changed && e->isDir && e->m_entries.count()) {
01564       Entry* sub_entry = 0;
01565       Q_FOREACH(sub_entry, e->m_entries) {
01566         if(e->isDir) { // ####### !?!? Already checked above
01567           if (QFileInfo(sub_entry->path).isDir()) // ##### !? no comparison between sub_entry->path and path?
01568             break;
01569         } else {
01570           if (QFileInfo(sub_entry->path).isFile())
01571             break;
01572         }
01573       }
01574       if (sub_entry) {
01575         removeEntry(0, e, sub_entry);
01576         //KDE_struct_stat stat_buf;
01577         //QByteArray tpath = QFile::encodeName(path);
01578         //KDE_stat(tpath, &stat_buf);
01579 
01580         if(!useQFSWatch(sub_entry))
01581 #ifdef HAVE_SYS_INOTIFY_H
01582           if(!useINotify(sub_entry))
01583 #endif
01584             useStat(sub_entry);
01585         fswEventReceived(sub_entry->path);
01586       }
01587     }
01588   }
01589 }
01590 #else
01591 void KDirWatchPrivate::fswEventReceived(const QString &path)
01592 {
01593     Q_UNUSED(path);
01594     kWarning (7001) << "QFileSystemWatcher event received but QFileSystemWatcher is not supported";
01595 }
01596 #endif    // HAVE_QFILESYSTEMWATCHER
01597 
01598 //
01599 // Class KDirWatch
01600 //
01601 
01602 K_GLOBAL_STATIC(KDirWatch, s_pKDirWatchSelf)
01603 KDirWatch* KDirWatch::self()
01604 {
01605   return s_pKDirWatchSelf;
01606 }
01607 
01608 bool KDirWatch::exists()
01609 {
01610   return s_pKDirWatchSelf != 0;
01611 }
01612 
01613 KDirWatch::KDirWatch (QObject* parent)
01614   : QObject(parent), d(createPrivate())
01615 {
01616   static int nameCounter = 0;
01617 
01618   nameCounter++;
01619   setObjectName(QString("KDirWatch-%1").arg(nameCounter) );
01620 
01621   d->ref();
01622 
01623   d->_isStopped = false;
01624 }
01625 
01626 KDirWatch::~KDirWatch()
01627 {
01628   d->removeEntries(this);
01629   if ( d->deref() )
01630   {
01631     // delete it if it's the last one
01632     delete d;
01633     dwp_self = 0;
01634   }
01635 }
01636 
01637 void KDirWatch::addDir( const QString& _path, WatchModes watchModes)
01638 {
01639   if (d) d->addEntry(this, _path, 0, true, watchModes);
01640 }
01641 
01642 void KDirWatch::addFile( const QString& _path )
01643 {
01644   if (d) d->addEntry(this, _path, 0, false);
01645 }
01646 
01647 QDateTime KDirWatch::ctime( const QString &_path ) const
01648 {
01649   KDirWatchPrivate::Entry* e = d->entry(_path);
01650 
01651   if (!e)
01652     return QDateTime();
01653 
01654   QDateTime result;
01655   result.setTime_t(e->m_ctime);
01656   return result;
01657 }
01658 
01659 void KDirWatch::removeDir( const QString& _path )
01660 {
01661   if (d) d->removeEntry(this, _path, 0);
01662 }
01663 
01664 void KDirWatch::removeFile( const QString& _path )
01665 {
01666   if (d) d->removeEntry(this, _path, 0);
01667 }
01668 
01669 bool KDirWatch::stopDirScan( const QString& _path )
01670 {
01671   if (d) {
01672     KDirWatchPrivate::Entry *e = d->entry(_path);
01673     if (e && e->isDir) return d->stopEntryScan(this, e);
01674   }
01675   return false;
01676 }
01677 
01678 bool KDirWatch::restartDirScan( const QString& _path )
01679 {
01680   if (d) {
01681     KDirWatchPrivate::Entry *e = d->entry(_path);
01682     if (e && e->isDir)
01683       // restart without notifying pending events
01684       return d->restartEntryScan(this, e, false);
01685   }
01686   return false;
01687 }
01688 
01689 void KDirWatch::stopScan()
01690 {
01691   if (d) {
01692     d->stopScan(this);
01693     d->_isStopped = true;
01694   }
01695 }
01696 
01697 bool KDirWatch::isStopped()
01698 {
01699   return d->_isStopped;
01700 }
01701 
01702 void KDirWatch::startScan( bool notify, bool skippedToo )
01703 {
01704   if (d) {
01705     d->_isStopped = false;
01706     d->startScan(this, notify, skippedToo);
01707   }
01708 }
01709 
01710 
01711 bool KDirWatch::contains( const QString& _path ) const
01712 {
01713   KDirWatchPrivate::Entry* e = d->entry(_path);
01714   if (!e)
01715      return false;
01716 
01717   foreach(KDirWatchPrivate::Client* client, e->m_clients) {
01718     if (client->instance == this)
01719       return true;
01720   }
01721 
01722   return false;
01723 }
01724 
01725 void KDirWatch::statistics()
01726 {
01727   if (!dwp_self) {
01728     kDebug(7001) << "KDirWatch not used";
01729     return;
01730   }
01731   dwp_self->statistics();
01732 }
01733 
01734 
01735 void KDirWatch::setCreated( const QString & _file )
01736 {
01737   kDebug(7001) << objectName() << "emitting created" << _file;
01738   emit created( _file );
01739 }
01740 
01741 void KDirWatch::setDirty( const QString & _file )
01742 {
01743   kDebug(7001) << objectName() << "emitting dirty" << _file;
01744   emit dirty( _file );
01745 }
01746 
01747 void KDirWatch::setDeleted( const QString & _file )
01748 {
01749   kDebug(7001) << objectName() << "emitting deleted" << _file;
01750   emit deleted( _file );
01751 }
01752 
01753 KDirWatch::Method KDirWatch::internalMethod()
01754 {
01755 #ifdef HAVE_FAM
01756   if (d->use_fam)
01757     return KDirWatch::FAM;
01758 #endif
01759 #ifdef HAVE_SYS_INOTIFY_H
01760   if (d->supports_inotify)
01761     return KDirWatch::INotify;
01762 #endif
01763   return KDirWatch::Stat;
01764 }
01765 
01766 
01767 #include "kdirwatch.moc"
01768 #include "kdirwatch_p.moc"
01769 
01770 //sven
01771 
01772 // vim: sw=2 ts=8 et

KIO

Skip menu "KIO"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.6.1
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal