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