• 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 QDebug operator<<(QDebug debug, const KDirWatchPrivate::Entry &entry)
00487 {
00488   debug.space() << entry.path << (entry.isDir ? "dir" : "file");
00489   if (entry.m_status == KDirWatchPrivate::NonExistent)
00490     debug << "NonExistent";
00491   debug << "mode:" << entry.m_mode
00492         << entry.m_clients.count() << "clients";
00493   if (!entry.m_entries.isEmpty())
00494     debug << entry.m_entries.count() << "nonexistent entries";
00495   return debug;
00496 }
00497 
00498 KDirWatchPrivate::Entry* KDirWatchPrivate::entry(const QString& _path)
00499 {
00500 // we only support absolute paths
00501   if (_path.isEmpty() || QDir::isRelativePath(_path)) {
00502     return 0;
00503   }
00504 
00505   QString path (_path);
00506 
00507   if ( path.length() > 1 && path.endsWith( QLatin1Char( '/' ) ) )
00508     path.truncate( path.length() - 1 );
00509 
00510   EntryMap::Iterator it = m_mapEntries.find( path );
00511   if ( it == m_mapEntries.end() )
00512     return 0;
00513   else
00514     return &(*it);
00515 }
00516 
00517 // set polling frequency for a entry and adjust global freq if needed
00518 void KDirWatchPrivate::useFreq(Entry* e, int newFreq)
00519 {
00520   e->freq = newFreq;
00521 
00522   // a reasonable frequency for the global polling timer
00523   if (e->freq < freq) {
00524     freq = e->freq;
00525     if (timer.isActive()) timer.start(freq);
00526     kDebug(7001) << "Global Poll Freq is now" << freq << "msec";
00527   }
00528 }
00529 
00530 
00531 #if defined(HAVE_FAM)
00532 // setup FAM notification, returns false if not possible
00533 bool KDirWatchPrivate::useFAM(Entry* e)
00534 {
00535   if (!use_fam) return false;
00536 
00537   // handle FAM events to avoid deadlock
00538   // (FAM sends back all files in a directory when monitoring)
00539   famEventReceived();
00540 
00541   e->m_mode = FAMMode;
00542   e->dirty = false;
00543 
00544   if (e->isDir) {
00545     if (e->m_status == NonExistent) {
00546       // If the directory does not exist we watch the parent directory
00547       addEntry(0, QDir::cleanPath(e->path+"/.."), e, true);
00548     }
00549     else {
00550       int res =FAMMonitorDirectory(&fc, QFile::encodeName(e->path),
00551                    &(e->fr), e);
00552       if (res<0) {
00553     e->m_mode = UnknownMode;
00554     use_fam=false;
00555         delete sn; sn = 0;
00556     return false;
00557       }
00558       kDebug(7001).nospace() << " Setup FAM (Req " << FAMREQUEST_GETREQNUM(&(e->fr))
00559                    << ") for " << e->path;
00560     }
00561   }
00562   else {
00563     if (e->m_status == NonExistent) {
00564       // If the file does not exist we watch the directory
00565       addEntry(0, QFileInfo(e->path).absolutePath(), e, true);
00566     }
00567     else {
00568       int res = FAMMonitorFile(&fc, QFile::encodeName(e->path),
00569                    &(e->fr), e);
00570       if (res<0) {
00571     e->m_mode = UnknownMode;
00572     use_fam=false;
00573         delete sn; sn = 0;
00574     return false;
00575       }
00576       // We need to make sure the entry isn't listed in its parent's subentries... (#222974, testMoveTo)
00577       removeEntry(0, QDir::cleanPath(e->path + "/.."), e);
00578 
00579       kDebug(7001).nospace() << " Setup FAM (Req " << FAMREQUEST_GETREQNUM(&(e->fr))
00580                    << ") for " << e->path;
00581     }
00582   }
00583 
00584   // handle FAM events to avoid deadlock
00585   // (FAM sends back all files in a directory when monitoring)
00586   famEventReceived();
00587 
00588   return true;
00589 }
00590 #endif
00591 
00592 #ifdef HAVE_SYS_INOTIFY_H
00593 // setup INotify notification, returns false if not possible
00594 bool KDirWatchPrivate::useINotify( Entry* e )
00595 {
00596   //kDebug (7001) << "trying to use inotify for monitoring";
00597 
00598   e->wd = 0;
00599   e->dirty = false;
00600 
00601   if (!supports_inotify) return false;
00602 
00603   e->m_mode = INotifyMode;
00604 
00605   if ( e->m_status == NonExistent ) {
00606     addEntry(0, QDir::cleanPath(e->path+"/.."), e, true);
00607     return true;
00608   }
00609 
00610   // May as well register for almost everything - it's free!
00611   int mask = IN_DELETE|IN_DELETE_SELF|IN_CREATE|IN_MOVE|IN_MOVE_SELF|IN_DONT_FOLLOW|IN_MOVED_FROM|IN_MODIFY|IN_ATTRIB;
00612 
00613   if ( ( e->wd = inotify_add_watch( m_inotify_fd,
00614                                     QFile::encodeName( e->path ), mask) ) > 0)
00615   {
00616     //kDebug (7001) << "inotify successfully used for monitoring";
00617     return true;
00618   }
00619 
00620   return false;
00621 }
00622 #endif
00623 #ifdef HAVE_QFILESYSTEMWATCHER
00624 bool KDirWatchPrivate::useQFSWatch(Entry* e)
00625 {
00626   e->m_mode = QFSWatchMode;
00627   e->dirty = false;
00628 
00629   if ( e->m_status == NonExistent ) {
00630     addEntry( 0, QDir::cleanPath( e->path + "/.." ), e, true );
00631     return true;
00632   }
00633 
00634   fsWatcher->addPath( e->path );
00635   return true;
00636 }
00637 #endif
00638 
00639 bool KDirWatchPrivate::useStat(Entry* e)
00640 {
00641   KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByPath(e->path);
00642   const bool slow = mp ? mp->probablySlow() : false;
00643   if (slow)
00644     useFreq(e, m_nfsPollInterval);
00645   else
00646     useFreq(e, m_PollInterval);
00647 
00648   if (e->m_mode != StatMode) {
00649     e->m_mode = StatMode;
00650     statEntries++;
00651 
00652     if ( statEntries == 1 ) {
00653       // if this was first STAT entry (=timer was stopped)
00654       timer.start(freq);      // then start the timer
00655       kDebug(7001) << " Started Polling Timer, freq " << freq;
00656     }
00657   }
00658 
00659   kDebug(7001) << " Setup Stat (freq " << e->freq << ") for " << e->path;
00660 
00661   return true;
00662 }
00663 
00664 
00665 /* If <instance> !=0, this KDirWatch instance wants to watch at <_path>,
00666  * providing in <isDir> the type of the entry to be watched.
00667  * Sometimes, entries are dependant on each other: if <sub_entry> !=0,
00668  * this entry needs another entry to watch himself (when notExistent).
00669  */
00670 void KDirWatchPrivate::addEntry(KDirWatch* instance, const QString& _path,
00671                 Entry* sub_entry, bool isDir, KDirWatch::WatchModes watchModes)
00672 {
00673   QString path (_path);
00674   if (path.isEmpty()
00675 #ifndef Q_WS_WIN
00676     || path.startsWith("/dev/") || (path == "/dev")
00677 #endif
00678   )
00679     return; // Don't even go there.
00680 
00681   if ( path.length() > 1 && path.endsWith( QLatin1Char( '/' ) ) )
00682     path.truncate( path.length() - 1 );
00683 
00684   EntryMap::Iterator it = m_mapEntries.find( path );
00685   if ( it != m_mapEntries.end() )
00686   {
00687     if (sub_entry) {
00688        (*it).m_entries.append(sub_entry);
00689        kDebug(7001) << "Added already watched Entry" << path
00690                     << "(for" << sub_entry->path << ")";
00691 #ifdef HAVE_SYS_INOTIFY_H
00692        Entry* e = &(*it);
00693        if( (e->m_mode == INotifyMode) && (e->wd > 0) ) {
00694          int mask = IN_DELETE|IN_DELETE_SELF|IN_CREATE|IN_MOVE|IN_MOVE_SELF|IN_DONT_FOLLOW;
00695          if(!e->isDir)
00696            mask |= IN_MODIFY|IN_ATTRIB;
00697          else
00698            mask |= IN_ONLYDIR;
00699 
00700          inotify_rm_watch (m_inotify_fd, e->wd);
00701          e->wd = inotify_add_watch( m_inotify_fd, QFile::encodeName( e->path ),
00702                                     mask);
00703        }
00704 #endif
00705     }
00706     else {
00707        (*it).addClient(instance, watchModes);
00708        kDebug(7001) << "Added already watched Entry" << path
00709              << "(now" <<  (*it).clients() << "clients)"
00710              << QString("[%1]").arg(instance->objectName());
00711     }
00712     return;
00713   }
00714 
00715   // we have a new path to watch
00716 
00717   KDE_struct_stat stat_buf;
00718   bool exists = (KDE::stat(path, &stat_buf) == 0);
00719 
00720   EntryMap::iterator newIt = m_mapEntries.insert( path, Entry() );
00721   // the insert does a copy, so we have to use <e> now
00722   Entry* e = &(*newIt);
00723 
00724   if (exists) {
00725     e->isDir = S_ISDIR(stat_buf.st_mode);
00726 
00727     if (e->isDir && !isDir) {
00728       KDE::lstat(path, &stat_buf);
00729       if (S_ISLNK(stat_buf.st_mode))
00730         // if it's a symlink, don't follow it
00731         e->isDir = false;
00732       else
00733         qWarning() << "KDirWatch:" << path << "is a directory. Use addDir!";
00734     } else if (!e->isDir && isDir)
00735       qWarning("KDirWatch: %s is a file. Use addFile!", qPrintable(path));
00736 
00737     if (!e->isDir && ( watchModes != KDirWatch::WatchDirOnly)) {
00738       qWarning() << "KDirWatch:" << path << "is a file. You can't use recursive or "
00739                     "watchFiles options";
00740       watchModes = KDirWatch::WatchDirOnly;
00741     }
00742 
00743 #ifdef Q_OS_WIN
00744     // ctime is the 'creation time' on windows - use mtime instead
00745     e->m_ctime = stat_buf.st_mtime;
00746 #else
00747     e->m_ctime = stat_buf.st_ctime;
00748 #endif
00749     e->m_status = Normal;
00750     e->m_nlink = stat_buf.st_nlink;
00751   }
00752   else {
00753     e->isDir = isDir;
00754     e->m_ctime = invalid_ctime;
00755     e->m_status = NonExistent;
00756     e->m_nlink = 0;
00757   }
00758 
00759   e->path = path;
00760   if (sub_entry)
00761     e->m_entries.append(sub_entry);
00762   else
00763     e->addClient(instance, watchModes);
00764 
00765   kDebug(7001).nospace() << "Added " << (e->isDir ? "Dir " : "File ") << path
00766     << (e->m_status == NonExistent ? " NotExisting" : "")
00767     << " for " << (sub_entry ? sub_entry->path : "")
00768     << " [" << (instance ? instance->objectName() : "") << "]";
00769 
00770   // now setup the notification method
00771   e->m_mode = UnknownMode;
00772   e->msecLeft = 0;
00773 
00774   if ( isNoisyFile( QFile::encodeName( path ) ) )
00775     return;
00776 
00777   if (exists && e->isDir && (watchModes != KDirWatch::WatchDirOnly)) {
00778     QFlags<QDir::Filter> filters = QDir::NoDotAndDotDot;
00779 
00780     if ((watchModes & KDirWatch::WatchSubDirs) &&
00781         (watchModes & KDirWatch::WatchFiles)) {
00782       filters |= (QDir::Dirs|QDir::Files);
00783     } else if (watchModes & KDirWatch::WatchSubDirs) {
00784       filters |= QDir::Dirs;
00785     } else if (watchModes & KDirWatch::WatchFiles) {
00786       filters |= QDir::Files;
00787     }
00788 
00789 #if defined(HAVE_SYS_INOTIFY_H)
00790     if (e->m_mode == INotifyMode || (e->m_mode == UnknownMode && m_preferredMethod == INotify)  )
00791     {
00792         kDebug (7001) << "Ignoring WatchFiles directive - this is implicit with inotify";
00793         // Placing a watch on individual files is redundant with inotify
00794         // (inotify gives us WatchFiles functionality "for free") and indeed
00795         // actively harmful, so prevent it.  WatchSubDirs is necessary, though.
00796         filters &= ~QDir::Files;
00797     }
00798 #endif
00799 
00800     QDir basedir (e->path);
00801     const QFileInfoList contents = basedir.entryInfoList(filters);
00802     for (QFileInfoList::const_iterator iter = contents.constBegin();
00803          iter != contents.constEnd(); ++iter)
00804     {
00805       const QFileInfo &fileInfo = *iter;
00806       // treat symlinks as files--don't follow them.
00807       bool isDir = fileInfo.isDir() && !fileInfo.isSymLink();
00808 
00809       addEntry (instance, fileInfo.absoluteFilePath(), 0, isDir,
00810                 isDir ? watchModes : KDirWatch::WatchDirOnly);
00811     }
00812   }
00813 
00814   // If the watch is on a network filesystem use the nfsPreferredMethod as the
00815   // default, otherwise use preferredMethod as the default, if the methods are
00816   // the same we can skip the mountpoint check
00817 
00818   // This allows to configure a different method for NFS mounts, since inotify
00819   // cannot detect changes made by other machines. However as a default inotify
00820   // is fine, since the most common case is a NFS-mounted home, where all changes
00821   // are made locally. #177892.
00822   WatchMethod preferredMethod = m_preferredMethod;
00823   if (m_nfsPreferredMethod != m_preferredMethod) {
00824     KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByPath(e->path);
00825     if (mountPoint && mountPoint->probablySlow()) {
00826       preferredMethod = m_nfsPreferredMethod;
00827     }
00828   }
00829 
00830   // Try the appropriate preferred method from the config first
00831   bool entryAdded = false;
00832   switch (preferredMethod) {
00833 #if defined(HAVE_FAM)
00834   case Fam: entryAdded = useFAM(e); break;
00835 #endif
00836 #if defined(HAVE_SYS_INOTIFY_H)
00837   case INotify: entryAdded = useINotify(e); break;
00838 #endif
00839 #if defined(HAVE_QFILESYSTEMWATCHER)
00840   case QFSWatch: entryAdded = useQFSWatch(e); break;
00841 #endif
00842   case Stat: entryAdded = useStat(e); break;
00843   default: break;
00844   }
00845 
00846   // Failing that try in order INotify, FAM, QFSWatch, Stat
00847   if (!entryAdded) {
00848 #if defined(HAVE_SYS_INOTIFY_H)
00849     if (useINotify(e)) return;
00850 #endif
00851 #if defined(HAVE_FAM)
00852     if (useFAM(e)) return;
00853 #endif
00854 #if defined(HAVE_QFILESYSTEMWATCHER)
00855     if (useQFSWatch(e)) return;
00856 #endif
00857     useStat(e);
00858   }
00859 }
00860 
00861 
00862 void KDirWatchPrivate::removeEntry(KDirWatch* instance,
00863                                    const QString& _path,
00864                                    Entry* sub_entry)
00865 {
00866   //kDebug(7001) << "path=" << _path << "sub_entry:" << sub_entry;
00867   Entry* e = entry(_path);
00868   if (!e) {
00869     kWarning(7001) << "doesn't know" << _path;
00870     return;
00871   }
00872 
00873   removeEntry(instance, e, sub_entry);
00874 }
00875 
00876 void KDirWatchPrivate::removeEntry(KDirWatch* instance,
00877                                    Entry* e,
00878                                    Entry* sub_entry)
00879 {
00880   removeList.remove(e);
00881 
00882   if (sub_entry)
00883     e->m_entries.removeAll(sub_entry);
00884   else
00885     e->removeClient(instance);
00886 
00887   if (e->m_clients.count() || e->m_entries.count())
00888     return;
00889 
00890   if (delayRemove) {
00891     removeList.insert(e);
00892     // now e->isValid() is false
00893     return;
00894   }
00895 
00896 #ifdef HAVE_FAM
00897   if (e->m_mode == FAMMode) {
00898     if ( e->m_status == Normal) {
00899       FAMCancelMonitor(&fc, &(e->fr) );
00900       kDebug(7001).nospace()  << "Cancelled FAM (Req " << FAMREQUEST_GETREQNUM(&(e->fr))
00901                     << ") for " << e->path;
00902     }
00903     else {
00904       if (e->isDir)
00905     removeEntry(0, QDir::cleanPath(e->path+"/.."), e);
00906       else
00907     removeEntry(0, QFileInfo(e->path).absolutePath(), e);
00908     }
00909   }
00910 #endif
00911 
00912 #ifdef HAVE_SYS_INOTIFY_H
00913   if (e->m_mode == INotifyMode) {
00914     if ( e->m_status == Normal ) {
00915       (void) inotify_rm_watch( m_inotify_fd, e->wd );
00916       kDebug(7001).nospace() << "Cancelled INotify (fd " << m_inotify_fd << ", "
00917                              << e->wd << ") for " << e->path;
00918     }
00919     else {
00920       if (e->isDir)
00921         removeEntry(0, QDir::cleanPath(e->path+"/.."), e);
00922       else
00923         removeEntry(0, QFileInfo(e->path).absolutePath(), e);
00924     }
00925   }
00926 #endif
00927 
00928 #ifdef HAVE_QFILESYSTEMWATCHER
00929   if (e->m_mode == QFSWatchMode) {
00930     fsWatcher->removePath(e->path);
00931   }
00932 #endif
00933   if (e->m_mode == StatMode) {
00934     statEntries--;
00935     if ( statEntries == 0 ) {
00936       timer.stop(); // stop timer if lists are empty
00937       kDebug(7001) << " Stopped Polling Timer";
00938     }
00939   }
00940 
00941   //kDebug(7001).nospace() << "Removed " << (e->isDir ? "Dir ":"File ") << e->path
00942   //   << " for " << (sub_entry ? sub_entry->path : "")
00943   //   << " [" << (instance ? instance->objectName() : "") << "]";
00944   m_mapEntries.remove( e->path ); // <e> not valid any more
00945 }
00946 
00947 
00948 /* Called from KDirWatch destructor:
00949  * remove <instance> as client from all entries
00950  */
00951 void KDirWatchPrivate::removeEntries( KDirWatch* instance )
00952 {
00953   int minfreq = 3600000;
00954 
00955   QStringList pathList;
00956   // put all entries where instance is a client in list
00957   EntryMap::Iterator it = m_mapEntries.begin();
00958   for( ; it != m_mapEntries.end(); ++it ) {
00959     Client* c = 0;
00960     foreach(Client* client, (*it).m_clients) {
00961       if (client->instance == instance) {
00962         c = client;
00963         break;
00964       }
00965     }
00966     if (c) {
00967       c->count = 1; // forces deletion of instance as client
00968       pathList.append((*it).path);
00969     }
00970     else if ( (*it).m_mode == StatMode && (*it).freq < minfreq )
00971       minfreq = (*it).freq;
00972   }
00973 
00974   foreach(const QString &path, pathList)
00975     removeEntry(instance, path, 0);
00976 
00977   if (minfreq > freq) {
00978     // we can decrease the global polling frequency
00979     freq = minfreq;
00980     if (timer.isActive()) timer.start(freq);
00981     kDebug(7001) << "Poll Freq now" << freq << "msec";
00982   }
00983 }
00984 
00985 // instance ==0: stop scanning for all instances
00986 bool KDirWatchPrivate::stopEntryScan( KDirWatch* instance, Entry* e)
00987 {
00988   int stillWatching = 0;
00989   foreach(Client* client, e->m_clients) {
00990     if (!instance || instance == client->instance)
00991       client->watchingStopped = true;
00992     else if (!client->watchingStopped)
00993       stillWatching += client->count;
00994   }
00995 
00996   kDebug(7001)  << (instance ? instance->objectName() : "all")
00997                 << "stopped scanning" << e->path << "(now"
00998                 << stillWatching << "watchers)";
00999 
01000   if (stillWatching == 0) {
01001     // if nobody is interested, we don't watch
01002     if ( e->m_mode != INotifyMode ) {
01003       e->m_ctime = invalid_ctime; // invalid
01004       e->m_status = NonExistent;
01005     }
01006     //    e->m_status = Normal;
01007   }
01008   return true;
01009 }
01010 
01011 // instance ==0: start scanning for all instances
01012 bool KDirWatchPrivate::restartEntryScan( KDirWatch* instance, Entry* e,
01013                      bool notify)
01014 {
01015   int wasWatching = 0, newWatching = 0;
01016   foreach(Client* client, e->m_clients) {
01017     if (!client->watchingStopped)
01018       wasWatching += client->count;
01019     else if (!instance || instance == client->instance) {
01020       client->watchingStopped = false;
01021       newWatching += client->count;
01022     }
01023   }
01024   if (newWatching == 0)
01025     return false;
01026 
01027   kDebug(7001)  << (instance ? instance->objectName() : "all")
01028                 << "restarted scanning" << e->path
01029                 << "(now" << wasWatching+newWatching << "watchers)";
01030 
01031   // restart watching and emit pending events
01032 
01033   int ev = NoChange;
01034   if (wasWatching == 0) {
01035     if (!notify) {
01036       KDE_struct_stat stat_buf;
01037       bool exists = (KDE::stat(e->path, &stat_buf) == 0);
01038       if (exists) {
01039 #ifdef Q_OS_WIN
01040         // ctime is the 'creation time' on windows - use mtime instead
01041         e->m_ctime = stat_buf.st_mtime;
01042 #else
01043         e->m_ctime = stat_buf.st_ctime;
01044 #endif
01045         e->m_status = Normal;
01046         e->m_nlink = stat_buf.st_nlink;
01047       }
01048       else {
01049         e->m_ctime = invalid_ctime;
01050         e->m_status = NonExistent;
01051         e->m_nlink = 0;
01052       }
01053     }
01054     e->msecLeft = 0;
01055     ev = scanEntry(e);
01056   }
01057   emitEvent(e,ev);
01058 
01059   return true;
01060 }
01061 
01062 // instance ==0: stop scanning for all instances
01063 void KDirWatchPrivate::stopScan(KDirWatch* instance)
01064 {
01065   EntryMap::Iterator it = m_mapEntries.begin();
01066   for( ; it != m_mapEntries.end(); ++it )
01067     stopEntryScan(instance, &(*it));
01068 }
01069 
01070 
01071 void KDirWatchPrivate::startScan(KDirWatch* instance,
01072                                  bool notify, bool skippedToo )
01073 {
01074   if (!notify)
01075     resetList(instance,skippedToo);
01076 
01077   EntryMap::Iterator it = m_mapEntries.begin();
01078   for( ; it != m_mapEntries.end(); ++it )
01079     restartEntryScan(instance, &(*it), notify);
01080 
01081   // timer should still be running when in polling mode
01082 }
01083 
01084 
01085 // clear all pending events, also from stopped
01086 void KDirWatchPrivate::resetList( KDirWatch* /*instance*/, bool skippedToo )
01087 {
01088   EntryMap::Iterator it = m_mapEntries.begin();
01089   for( ; it != m_mapEntries.end(); ++it ) {
01090 
01091     foreach(Client* client, (*it).m_clients) {
01092       if (!client->watchingStopped || skippedToo)
01093         client->pending = NoChange;
01094     }
01095   }
01096 }
01097 
01098 // Return event happened on <e>
01099 //
01100 int KDirWatchPrivate::scanEntry(Entry* e)
01101 {
01102 #ifdef HAVE_FAM
01103   if (e->m_mode == FAMMode) {
01104     // we know nothing has changed, no need to stat
01105     if(!e->dirty) return NoChange;
01106     e->dirty = false;
01107   }
01108 #endif
01109 
01110   // Shouldn't happen: Ignore "unknown" notification method
01111   if (e->m_mode == UnknownMode) return NoChange;
01112 
01113 #if defined( HAVE_SYS_INOTIFY_H )
01114   if (e->m_mode == DNotifyMode || e->m_mode == INotifyMode ) {
01115     // we know nothing has changed, no need to stat
01116     if(!e->dirty) return NoChange;
01117     e->dirty = false;
01118   }
01119 #endif
01120 
01121 #if defined( HAVE_QFILESYSTEMWATCHER )
01122   if (e->m_mode == QFSWatchMode ) {
01123     // we know nothing has changed, no need to stat
01124     if(!e->dirty) return NoChange;
01125     e->dirty = false;
01126   }
01127 #endif
01128 
01129   if (e->m_mode == StatMode) {
01130     // only scan if timeout on entry timer happens;
01131     // e.g. when using 500msec global timer, a entry
01132     // with freq=5000 is only watched every 10th time
01133 
01134     e->msecLeft -= freq;
01135     if (e->msecLeft>0) return NoChange;
01136     e->msecLeft += e->freq;
01137   }
01138 
01139   KDE_struct_stat stat_buf;
01140   bool exists = (KDE::stat(e->path, &stat_buf) == 0);
01141   if (exists) {
01142 
01143     if (e->m_status == NonExistent) {
01144 #ifdef Q_OS_WIN
01145       // ctime is the 'creation time' on windows - use mtime instead
01146       e->m_ctime = stat_buf.st_mtime;
01147 #else
01148       e->m_ctime = stat_buf.st_ctime;
01149 #endif
01150       e->m_status = Normal;
01151       e->m_nlink = stat_buf.st_nlink;
01152       return Created;
01153     }
01154 
01155 #ifdef Q_OS_WIN
01156     stat_buf.st_ctime = stat_buf.st_mtime;
01157 #endif
01158     if ( (e->m_ctime != invalid_ctime) &&
01159           ((stat_buf.st_ctime != e->m_ctime) ||
01160           (stat_buf.st_nlink != (nlink_t) e->m_nlink))
01161 #if defined( HAVE_QFILESYSTEMWATCHER )
01162           // we trust QFSW to get it right, the ctime comparisons above
01163           // fail for example when adding files to directories on Windows
01164           // which doesn't change the mtime of the directory
01165         ||(e->m_mode == QFSWatchMode )
01166 #endif
01167     ) {
01168       e->m_ctime = stat_buf.st_ctime;
01169       e->m_nlink = stat_buf.st_nlink;
01170       return Changed;
01171     }
01172 
01173     return NoChange;
01174   }
01175 
01176   // dir/file doesn't exist
01177 
01178   if (e->m_ctime == invalid_ctime) {
01179     e->m_nlink = 0;
01180     e->m_status = NonExistent;
01181     return NoChange;
01182   }
01183 
01184   e->m_ctime = invalid_ctime;
01185   e->m_nlink = 0;
01186   e->m_status = NonExistent;
01187 
01188   return Deleted;
01189 }
01190 
01191 /* Notify all interested KDirWatch instances about a given event on an entry
01192  * and stored pending events. When watching is stopped, the event is
01193  * added to the pending events.
01194  */
01195 void KDirWatchPrivate::emitEvent(const Entry* e, int event, const QString &fileName)
01196 {
01197   QString path (e->path);
01198   if (!fileName.isEmpty()) {
01199     if (!QDir::isRelativePath(fileName))
01200       path = fileName;
01201     else
01202 #ifdef Q_OS_UNIX
01203       path += '/' + fileName;
01204 #elif defined(Q_WS_WIN)
01205       //current drive is passed instead of /
01206       path += QDir::currentPath().left(2) + '/' + fileName;
01207 #endif
01208   }
01209 
01210   foreach(Client* c, e->m_clients)
01211   {
01212     if (c->instance==0 || c->count==0) continue;
01213 
01214     if (c->watchingStopped) {
01215       // add event to pending...
01216       if (event == Changed)
01217         c->pending |= event;
01218       else if (event == Created || event == Deleted)
01219         c->pending = event;
01220       continue;
01221     }
01222     // not stopped
01223     if (event == NoChange || event == Changed)
01224       event |= c->pending;
01225     c->pending = NoChange;
01226     if (event == NoChange) continue;
01227 
01228     // Emit the signals delayed, to avoid unexpected re-entrancy from the slots (#220153)
01229     
01230     if (event & Deleted) {
01231       QMetaObject::invokeMethod(c->instance, "setDeleted", Qt::QueuedConnection, Q_ARG(QString, path));
01232       // emit only Deleted event...
01233       continue;
01234     }
01235 
01236     if (event & Created) {
01237       QMetaObject::invokeMethod(c->instance, "setCreated", Qt::QueuedConnection, Q_ARG(QString, path));
01238       // possible emit Change event after creation
01239     }
01240 
01241     if (event & Changed) {
01242       QMetaObject::invokeMethod(c->instance, "setDirty", Qt::QueuedConnection, Q_ARG(QString, path));
01243     }
01244   }
01245 }
01246 
01247 // Remove entries which were marked to be removed
01248 void KDirWatchPrivate::slotRemoveDelayed()
01249 {
01250   delayRemove = false;
01251   // Removing an entry could also take care of removing its parent
01252   // (e.g. in FAM or inotify mode), which would remove other entries in removeList,
01253   // so don't use foreach or iterators here...
01254   while (!removeList.isEmpty()) {
01255     Entry* entry = *removeList.begin();
01256     removeEntry(0, entry, 0); // this will remove entry from removeList
01257   }
01258 }
01259 
01260 /* Scan all entries to be watched for changes. This is done regularly
01261  * when polling. This is NOT used by FAM.
01262  */
01263 void KDirWatchPrivate::slotRescan()
01264 {
01265   EntryMap::Iterator it;
01266 
01267   // People can do very long things in the slot connected to dirty(),
01268   // like showing a message box. We don't want to keep polling during
01269   // that time, otherwise the value of 'delayRemove' will be reset.
01270   bool timerRunning = timer.isActive();
01271   if ( timerRunning )
01272     timer.stop();
01273 
01274   // We delay deletions of entries this way.
01275   // removeDir(), when called in slotDirty(), can cause a crash otherwise
01276   delayRemove = true;
01277 
01278   if (rescan_all)
01279   {
01280     // mark all as dirty
01281     it = m_mapEntries.begin();
01282     for( ; it != m_mapEntries.end(); ++it )
01283       (*it).dirty = true;
01284     rescan_all = false;
01285   }
01286   else
01287   {
01288     // progate dirty flag to dependant entries (e.g. file watches)
01289     it = m_mapEntries.begin();
01290     for( ; it != m_mapEntries.end(); ++it )
01291       if (((*it).m_mode == INotifyMode || (*it).m_mode == DNotifyMode) && (*it).dirty )
01292         (*it).propagate_dirty();
01293   }
01294 
01295 #ifdef HAVE_SYS_INOTIFY_H
01296   QList<Entry*> dList, cList;
01297 #endif
01298 
01299   it = m_mapEntries.begin();
01300   for( ; it != m_mapEntries.end(); ++it ) {
01301     // we don't check invalid entries (i.e. remove delayed)
01302     if (!(*it).isValid()) continue;
01303 
01304     int ev = scanEntry( &(*it) );
01305 
01306 #ifdef HAVE_SYS_INOTIFY_H
01307     if ((*it).m_mode == INotifyMode) {
01308       if ( ev == Deleted ) {
01309         addEntry(0, QDir::cleanPath( ( *it ).path+"/.."), &*it, true);
01310       }
01311     }
01312     if ((*it).m_mode == INotifyMode && ev == Created && (*it).wd == 0) {
01313       cList.append( &(*it) );
01314       if (! useINotify( &(*it) )) {
01315         useStat( &(*it) );
01316       }
01317     }
01318 
01319     if ((*it).isDir)
01320     {
01321       // Report and clear the the list of files that have changed in this directory.
01322       // Remove duplicates by changing to set and back again:
01323       // we don't really care about preserving the order of the
01324       // original changes.
01325       QList<QString> pendingFileChanges = (*it).m_pendingFileChanges.toSet().toList();
01326       Q_FOREACH(QString changedFilename, pendingFileChanges )
01327       {
01328         emitEvent(&(*it), Changed, changedFilename);
01329       }
01330       (*it).m_pendingFileChanges.clear();
01331     }
01332 #endif
01333 
01334     if ( ev != NoChange )
01335       emitEvent( &(*it), ev);
01336   }
01337 
01338   if ( timerRunning )
01339     timer.start(freq);
01340 
01341 #ifdef HAVE_SYS_INOTIFY_H
01342   // Remove watch of parent of new created directories
01343   Q_FOREACH(Entry* e, cList)
01344     removeEntry(0, QDir::cleanPath( e->path+"/.."), e);
01345 #endif
01346 
01347   QTimer::singleShot(0, this, SLOT(slotRemoveDelayed()));
01348 }
01349 
01350 bool KDirWatchPrivate::isNoisyFile( const char * filename )
01351 {
01352   // $HOME/.X.err grows with debug output, so don't notify change
01353   if ( *filename == '.') {
01354     if (strncmp(filename, ".X.err", 6) == 0) return true;
01355     if (strncmp(filename, ".xsession-errors", 16) == 0) return true;
01356     // fontconfig updates the cache on every KDE app start
01357     // (inclusive kio_thumbnail slaves)
01358     if (strncmp(filename, ".fonts.cache", 12) == 0) return true;
01359   }
01360 
01361   return false;
01362 }
01363 
01364 #ifdef HAVE_FAM
01365 void KDirWatchPrivate::famEventReceived()
01366 {
01367   static FAMEvent fe;
01368 
01369   delayRemove = true;
01370 
01371   //kDebug(7001) << "Fam event received";
01372 
01373   while(use_fam && FAMPending(&fc)) {
01374     if (FAMNextEvent(&fc, &fe) == -1) {
01375       kWarning(7001) << "FAM connection problem, switching to polling.";
01376       use_fam = false;
01377       delete sn; sn = 0;
01378 
01379       // Replace all FAMMode entries with DNotify/Stat
01380       EntryMap::Iterator it;
01381       it = m_mapEntries.begin();
01382       for( ; it != m_mapEntries.end(); ++it )
01383         if ((*it).m_mode == FAMMode && (*it).m_clients.count()>0) {
01384 #ifdef HAVE_SYS_INOTIFY_H
01385           if (useINotify( &(*it) )) continue;
01386 #endif
01387           useStat( &(*it) );
01388         }
01389     }
01390     else
01391       checkFAMEvent(&fe);
01392   }
01393 
01394   QTimer::singleShot(0, this, SLOT(slotRemoveDelayed()));
01395 }
01396 
01397 void KDirWatchPrivate::checkFAMEvent(FAMEvent* fe)
01398 {
01399   //kDebug(7001);
01400 
01401   // Don't be too verbose ;-)
01402   if ((fe->code == FAMExists) ||
01403       (fe->code == FAMEndExist) ||
01404       (fe->code == FAMAcknowledge)) return;
01405 
01406   if ( isNoisyFile( fe->filename ) )
01407     return;
01408 
01409   Entry* e = 0;
01410   EntryMap::Iterator it = m_mapEntries.begin();
01411   for( ; it != m_mapEntries.end(); ++it )
01412     if (FAMREQUEST_GETREQNUM(&( (*it).fr )) ==
01413        FAMREQUEST_GETREQNUM(&(fe->fr)) ) {
01414       e = &(*it);
01415       break;
01416     }
01417 
01418   // Entry* e = static_cast<Entry*>(fe->userdata);
01419 
01420 #if 0 // #88538
01421   kDebug(7001)  << "Processing FAM event ("
01422                 << ((fe->code == FAMChanged) ? "FAMChanged" :
01423                     (fe->code == FAMDeleted) ? "FAMDeleted" :
01424                     (fe->code == FAMStartExecuting) ? "FAMStartExecuting" :
01425                     (fe->code == FAMStopExecuting) ? "FAMStopExecuting" :
01426                     (fe->code == FAMCreated) ? "FAMCreated" :
01427                     (fe->code == FAMMoved) ? "FAMMoved" :
01428                     (fe->code == FAMAcknowledge) ? "FAMAcknowledge" :
01429                     (fe->code == FAMExists) ? "FAMExists" :
01430                     (fe->code == FAMEndExist) ? "FAMEndExist" : "Unknown Code")
01431                 << ", " << fe->filename
01432                 << ", Req " << FAMREQUEST_GETREQNUM(&(fe->fr)) << ")";
01433 #endif
01434 
01435   if (!e) {
01436     // this happens e.g. for FAMAcknowledge after deleting a dir...
01437     //    kDebug(7001) << "No entry for FAM event ?!";
01438     return;
01439   }
01440 
01441   if (e->m_status == NonExistent) {
01442     kDebug(7001) << "FAM event for nonExistent entry " << e->path;
01443     return;
01444   }
01445 
01446   // Delayed handling. This rechecks changes with own stat calls.
01447   e->dirty = true;
01448   if (!rescan_timer.isActive())
01449     rescan_timer.start(m_PollInterval); // singleshot
01450 
01451   // needed FAM control actions on FAM events
01452   if (e->isDir)
01453     switch (fe->code)
01454     {
01455       case FAMDeleted:
01456        // file absolute: watched dir
01457         if (!QDir::isRelativePath(fe->filename))
01458         {
01459           // a watched directory was deleted
01460 
01461           e->m_status = NonExistent;
01462           FAMCancelMonitor(&fc, &(e->fr) ); // needed ?
01463           kDebug(7001)  << "Cancelled FAMReq"
01464                         << FAMREQUEST_GETREQNUM(&(e->fr))
01465                         << "for" << e->path;
01466           // Scan parent for a new creation
01467           addEntry(0, QDir::cleanPath( e->path+"/.."), e, true);
01468         }
01469         break;
01470 
01471       case FAMCreated: {
01472           // check for creation of a directory we have to watch
01473         QString tpath(e->path + QLatin1Char('/') + fe->filename);
01474 
01475         Entry* sub_entry = e->findSubEntry(tpath);
01476         if (sub_entry && sub_entry->isDir) {
01477           removeEntry(0, e, sub_entry);
01478           sub_entry->m_status = Normal;
01479           if (!useFAM(sub_entry)) {
01480 #ifdef HAVE_SYS_INOTIFY_H
01481             if (!useINotify(sub_entry ))
01482 #endif
01483               useStat(sub_entry);
01484           }
01485         }
01486         else if ((sub_entry == 0) && (!e->m_clients.empty())) {
01487           bool isDir = false;
01488           const QList<Client *> clients = e->clientsForFileOrDir(tpath, &isDir);
01489           Q_FOREACH(Client *client, clients) {
01490             addEntry (client->instance, tpath, 0, isDir,
01491                       isDir ? client->m_watchModes : KDirWatch::WatchDirOnly);
01492           }
01493 
01494           if (!clients.isEmpty()) {
01495             emitEvent(e, Created, tpath);
01496 
01497             QString msg (QString::number(clients.count()));
01498             msg += " instance/s monitoring the new ";
01499             msg += (isDir ? "dir " : "file ") + tpath;
01500             kDebug(7001) << msg;
01501           }
01502         }
01503       }
01504         break;
01505       default:
01506         break;
01507     }
01508 }
01509 #else
01510 void KDirWatchPrivate::famEventReceived()
01511 {
01512     kWarning (7001) << "Fam event received but FAM is not supported";
01513 }
01514 #endif
01515 
01516 
01517 void KDirWatchPrivate::statistics()
01518 {
01519   EntryMap::Iterator it;
01520 
01521   kDebug(7001) << "Entries watched:";
01522   if (m_mapEntries.count()==0) {
01523     kDebug(7001) << "  None.";
01524   }
01525   else {
01526     it = m_mapEntries.begin();
01527     for( ; it != m_mapEntries.end(); ++it ) {
01528       Entry* e = &(*it);
01529       kDebug(7001)  << "  " << e->path << " ("
01530                     << ((e->m_status==Normal)?"":"Nonexistent ")
01531                     << (e->isDir ? "Dir":"File") << ", using "
01532                     << ((e->m_mode == FAMMode) ? "FAM" :
01533                         (e->m_mode == INotifyMode) ? "INotify" :
01534                         (e->m_mode == DNotifyMode) ? "DNotify" :
01535                         (e->m_mode == QFSWatchMode) ? "QFSWatch" :
01536                         (e->m_mode == StatMode) ? "Stat" : "Unknown Method")
01537                     << ")";
01538 
01539       foreach(Client* c, e->m_clients) {
01540         QString pending;
01541         if (c->watchingStopped) {
01542           if (c->pending & Deleted) pending += "deleted ";
01543           if (c->pending & Created) pending += "created ";
01544           if (c->pending & Changed) pending += "changed ";
01545           if (!pending.isEmpty()) pending = " (pending: " + pending + ')';
01546           pending = ", stopped" + pending;
01547         }
01548         kDebug(7001)  << "    by " << c->instance->objectName()
01549                       << " (" << c->count << " times)" << pending;
01550       }
01551       if (e->m_entries.count()>0) {
01552         kDebug(7001) << "    dependent entries:";
01553         foreach(Entry *d, e->m_entries) {
01554           kDebug(7001) << "      " << d->path;
01555         }
01556       }
01557     }
01558   }
01559 }
01560 
01561 #ifdef HAVE_QFILESYSTEMWATCHER
01562 // Slot for QFileSystemWatcher
01563 void KDirWatchPrivate::fswEventReceived(const QString &path)
01564 {
01565   EntryMap::Iterator it;
01566   it = m_mapEntries.find(path);
01567   if(it != m_mapEntries.end()) {
01568     Entry entry = *it;  // deep copy to not point to uninialized data (can happen inside emitEvent() )
01569     Entry *e = &entry;
01570     e->dirty = true;
01571     int ev = scanEntry(e);
01572     if (ev != NoChange)
01573       emitEvent(e, ev);
01574     if(ev == Deleted) {
01575       if (e->isDir)
01576         addEntry(0, QDir::cleanPath(e->path + "/.."), e, true);
01577       else
01578         addEntry(0, QFileInfo(e->path).absolutePath(), e, true);
01579     } else
01580     if (ev == Changed && e->isDir && e->m_entries.count()) {
01581       Entry* sub_entry = 0;
01582       Q_FOREACH(sub_entry, e->m_entries) {
01583         if(e->isDir) { // ####### !?!? Already checked above
01584           if (QFileInfo(sub_entry->path).isDir()) // ##### !? no comparison between sub_entry->path and path?
01585             break;
01586         } else {
01587           if (QFileInfo(sub_entry->path).isFile())
01588             break;
01589         }
01590       }
01591       if (sub_entry) {
01592         removeEntry(0, e, sub_entry);
01593         //KDE_struct_stat stat_buf;
01594         //QByteArray tpath = QFile::encodeName(path);
01595         //KDE_stat(tpath, &stat_buf);
01596 
01597         if(!useQFSWatch(sub_entry))
01598 #ifdef HAVE_SYS_INOTIFY_H
01599           if(!useINotify(sub_entry))
01600 #endif
01601             useStat(sub_entry);
01602         fswEventReceived(sub_entry->path);
01603       }
01604     }
01605   }
01606 }
01607 #else
01608 void KDirWatchPrivate::fswEventReceived(const QString &path)
01609 {
01610     Q_UNUSED(path);
01611     kWarning (7001) << "QFileSystemWatcher event received but QFileSystemWatcher is not supported";
01612 }
01613 #endif    // HAVE_QFILESYSTEMWATCHER
01614 
01615 //
01616 // Class KDirWatch
01617 //
01618 
01619 K_GLOBAL_STATIC(KDirWatch, s_pKDirWatchSelf)
01620 KDirWatch* KDirWatch::self()
01621 {
01622   return s_pKDirWatchSelf;
01623 }
01624 
01625 bool KDirWatch::exists()
01626 {
01627   return s_pKDirWatchSelf != 0;
01628 }
01629 
01630 KDirWatch::KDirWatch (QObject* parent)
01631   : QObject(parent), d(createPrivate())
01632 {
01633   static int nameCounter = 0;
01634 
01635   nameCounter++;
01636   setObjectName(QString("KDirWatch-%1").arg(nameCounter) );
01637 
01638   d->ref();
01639 
01640   d->_isStopped = false;
01641 }
01642 
01643 KDirWatch::~KDirWatch()
01644 {
01645   d->removeEntries(this);
01646   if ( d->deref() )
01647   {
01648     // delete it if it's the last one
01649     delete d;
01650     dwp_self = 0;
01651   }
01652 }
01653 
01654 void KDirWatch::addDir( const QString& _path, WatchModes watchModes)
01655 {
01656   if (d) d->addEntry(this, _path, 0, true, watchModes);
01657 }
01658 
01659 void KDirWatch::addFile( const QString& _path )
01660 {
01661   if (d) d->addEntry(this, _path, 0, false);
01662 }
01663 
01664 QDateTime KDirWatch::ctime( const QString &_path ) const
01665 {
01666   KDirWatchPrivate::Entry* e = d->entry(_path);
01667 
01668   if (!e)
01669     return QDateTime();
01670 
01671   QDateTime result;
01672   result.setTime_t(e->m_ctime);
01673   return result;
01674 }
01675 
01676 void KDirWatch::removeDir( const QString& _path )
01677 {
01678   if (d) d->removeEntry(this, _path, 0);
01679 }
01680 
01681 void KDirWatch::removeFile( const QString& _path )
01682 {
01683   if (d) d->removeEntry(this, _path, 0);
01684 }
01685 
01686 bool KDirWatch::stopDirScan( const QString& _path )
01687 {
01688   if (d) {
01689     KDirWatchPrivate::Entry *e = d->entry(_path);
01690     if (e && e->isDir) return d->stopEntryScan(this, e);
01691   }
01692   return false;
01693 }
01694 
01695 bool KDirWatch::restartDirScan( const QString& _path )
01696 {
01697   if (d) {
01698     KDirWatchPrivate::Entry *e = d->entry(_path);
01699     if (e && e->isDir)
01700       // restart without notifying pending events
01701       return d->restartEntryScan(this, e, false);
01702   }
01703   return false;
01704 }
01705 
01706 void KDirWatch::stopScan()
01707 {
01708   if (d) {
01709     d->stopScan(this);
01710     d->_isStopped = true;
01711   }
01712 }
01713 
01714 bool KDirWatch::isStopped()
01715 {
01716   return d->_isStopped;
01717 }
01718 
01719 void KDirWatch::startScan( bool notify, bool skippedToo )
01720 {
01721   if (d) {
01722     d->_isStopped = false;
01723     d->startScan(this, notify, skippedToo);
01724   }
01725 }
01726 
01727 
01728 bool KDirWatch::contains( const QString& _path ) const
01729 {
01730   KDirWatchPrivate::Entry* e = d->entry(_path);
01731   if (!e)
01732      return false;
01733 
01734   foreach(KDirWatchPrivate::Client* client, e->m_clients) {
01735     if (client->instance == this)
01736       return true;
01737   }
01738 
01739   return false;
01740 }
01741 
01742 void KDirWatch::statistics()
01743 {
01744   if (!dwp_self) {
01745     kDebug(7001) << "KDirWatch not used";
01746     return;
01747   }
01748   dwp_self->statistics();
01749 }
01750 
01751 
01752 void KDirWatch::setCreated( const QString & _file )
01753 {
01754   kDebug(7001) << objectName() << "emitting created" << _file;
01755   emit created( _file );
01756 }
01757 
01758 void KDirWatch::setDirty( const QString & _file )
01759 {
01760   kDebug(7001) << objectName() << "emitting dirty" << _file;
01761   emit dirty( _file );
01762 }
01763 
01764 void KDirWatch::setDeleted( const QString & _file )
01765 {
01766   kDebug(7001) << objectName() << "emitting deleted" << _file;
01767   emit deleted( _file );
01768 }
01769 
01770 KDirWatch::Method KDirWatch::internalMethod()
01771 {
01772 #ifdef HAVE_FAM
01773   if (d->use_fam)
01774     return KDirWatch::FAM;
01775 #endif
01776 #ifdef HAVE_SYS_INOTIFY_H
01777   if (d->supports_inotify)
01778     return KDirWatch::INotify;
01779 #endif
01780   return KDirWatch::Stat;
01781 }
01782 
01783 
01784 #include "kdirwatch.moc"
01785 #include "kdirwatch_p.moc"
01786 
01787 //sven
01788 
01789 // 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