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