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