File indexing completed on 2024-05-12 03:54:55
0001 /* This file is part of the KDE libraries 0002 SPDX-FileCopyrightText: 1998 Sven Radej <sven@lisa.exp.univie.ac.at> 0003 SPDX-FileCopyrightText: 2006 Dirk Mueller <mueller@kde.org> 0004 SPDX-FileCopyrightText: 2007 Flavio Castelli <flavio.castelli@gmail.com> 0005 SPDX-FileCopyrightText: 2008 Rafal Rzepecki <divided.mind@gmail.com> 0006 SPDX-FileCopyrightText: 2010 David Faure <faure@kde.org> 0007 SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org> 0008 0009 SPDX-License-Identifier: LGPL-2.0-only 0010 */ 0011 0012 // CHANGES: 0013 // Jul 30, 2008 - Don't follow symlinks when recursing to avoid loops (Rafal) 0014 // Aug 6, 2007 - KDirWatch::WatchModes support complete, flags work fine also 0015 // when using FAMD (Flavio Castelli) 0016 // Aug 3, 2007 - Handled KDirWatch::WatchModes flags when using inotify, now 0017 // recursive and file monitoring modes are implemented (Flavio Castelli) 0018 // Jul 30, 2007 - Substituted addEntry boolean params with KDirWatch::WatchModes 0019 // flag (Flavio Castelli) 0020 // Oct 4, 2005 - Inotify support (Dirk Mueller) 0021 // February 2002 - Add file watching and remote mount check for STAT 0022 // Mar 30, 2001 - Native support for Linux dir change notification. 0023 // Jan 28, 2000 - Usage of FAM service on IRIX (Josef.Weidendorfer@in.tum.de) 0024 // May 24. 1998 - List of times introduced, and some bugs are fixed. (sven) 0025 // May 23. 1998 - Removed static pointer - you can have more instances. 0026 // It was Needed for KRegistry. KDirWatch now emits signals and doesn't 0027 // call (or need) KFM. No more URL's - just plain paths. (sven) 0028 // Mar 29. 1998 - added docs, stop/restart for particular Dirs and 0029 // deep copies for list of dirs. (sven) 0030 // Mar 28. 1998 - Created. (sven) 0031 0032 #include "kdirwatch.h" 0033 #include "kcoreaddons_debug.h" 0034 #include "kdirwatch_p.h" 0035 #include "kfilesystemtype.h" 0036 #include "knetworkmounts.h" 0037 0038 #include <io/config-kdirwatch.h> 0039 0040 #include <QCoreApplication> 0041 #include <QDir> 0042 #include <QFile> 0043 #include <QLoggingCategory> 0044 #include <QSocketNotifier> 0045 #include <QThread> 0046 #include <QThreadStorage> 0047 #include <QTimer> 0048 #include <assert.h> 0049 #include <cerrno> 0050 #include <sys/stat.h> 0051 0052 #include <qplatformdefs.h> // QT_LSTAT, QT_STAT, QT_STATBUF 0053 0054 #include <stdlib.h> 0055 #include <string.h> 0056 0057 #if HAVE_SYS_INOTIFY_H 0058 #include <fcntl.h> 0059 #include <sys/inotify.h> 0060 #include <unistd.h> 0061 0062 #ifndef IN_DONT_FOLLOW 0063 #define IN_DONT_FOLLOW 0x02000000 0064 #endif 0065 0066 #ifndef IN_ONLYDIR 0067 #define IN_ONLYDIR 0x01000000 0068 #endif 0069 0070 // debug 0071 #include <sys/ioctl.h> 0072 0073 #include <sys/utsname.h> 0074 0075 #endif // HAVE_SYS_INOTIFY_H 0076 0077 Q_DECLARE_LOGGING_CATEGORY(KDIRWATCH) 0078 // logging category for this framework, default: log stuff >= warning 0079 Q_LOGGING_CATEGORY(KDIRWATCH, "kf.coreaddons.kdirwatch", QtWarningMsg) 0080 0081 // set this to true for much more verbose debug output 0082 static bool s_verboseDebug = false; 0083 0084 static QThreadStorage<KDirWatchPrivate *> dwp_self; 0085 static KDirWatchPrivate *createPrivate() 0086 { 0087 if (!dwp_self.hasLocalData()) { 0088 dwp_self.setLocalData(new KDirWatchPrivate); 0089 } 0090 return dwp_self.localData(); 0091 } 0092 static void destroyPrivate() 0093 { 0094 dwp_self.localData()->deleteLater(); 0095 dwp_self.setLocalData(nullptr); 0096 } 0097 0098 // Convert a string into a watch Method 0099 static KDirWatch::Method methodFromString(const QByteArray &method) 0100 { 0101 if (method == "Stat") { 0102 return KDirWatch::Stat; 0103 } else if (method == "QFSWatch") { 0104 return KDirWatch::QFSWatch; 0105 } else { 0106 #if HAVE_SYS_INOTIFY_H 0107 // inotify supports delete+recreate+modify, which QFSWatch doesn't support 0108 return KDirWatch::INotify; 0109 #else 0110 return KDirWatch::QFSWatch; 0111 #endif 0112 } 0113 } 0114 0115 static const char *methodToString(KDirWatch::Method method) 0116 { 0117 switch (method) { 0118 case KDirWatch::INotify: 0119 return "INotify"; 0120 case KDirWatch::Stat: 0121 return "Stat"; 0122 case KDirWatch::QFSWatch: 0123 return "QFSWatch"; 0124 } 0125 // not reached 0126 return nullptr; 0127 } 0128 0129 static const char s_envNfsPoll[] = "KDIRWATCH_NFSPOLLINTERVAL"; 0130 static const char s_envPoll[] = "KDIRWATCH_POLLINTERVAL"; 0131 static const char s_envMethod[] = "KDIRWATCH_METHOD"; 0132 static const char s_envNfsMethod[] = "KDIRWATCH_NFSMETHOD"; 0133 0134 // 0135 // Class KDirWatchPrivate (singleton) 0136 // 0137 0138 /* All entries (files/directories) to be watched in the 0139 * application (coming from multiple KDirWatch instances) 0140 * are registered in a single KDirWatchPrivate instance. 0141 * 0142 * At the moment, the following methods for file watching 0143 * are supported: 0144 * - Polling: All files to be watched are polled regularly 0145 * using stat (more precise: QFileInfo.lastModified()). 0146 * The polling frequency is determined from global kconfig 0147 * settings, defaulting to 500 ms for local directories 0148 * and 5000 ms for remote mounts 0149 * - FAM (File Alternation Monitor): first used on IRIX, SGI 0150 * has ported this method to LINUX. It uses a kernel part 0151 * (IMON, sending change events to /dev/imon) and a user 0152 * level daemon (fam), to which applications connect for 0153 * notification of file changes. For NFS, the fam daemon 0154 * on the NFS server machine is used; if IMON is not built 0155 * into the kernel, fam uses polling for local files. 0156 * - INOTIFY: In LINUX 2.6.13, inode change notification was 0157 * introduced. You're now able to watch arbitrary inode's 0158 * for changes, and even get notification when they're 0159 * unmounted. 0160 */ 0161 0162 KDirWatchPrivate::KDirWatchPrivate() 0163 : m_statRescanTimer() 0164 , freq(3600000) 0165 , // 1 hour as upper bound 0166 statEntries(0) 0167 , delayRemove(false) 0168 , rescan_all(false) 0169 , rescan_timer() 0170 , 0171 #if HAVE_SYS_INOTIFY_H 0172 mSn(nullptr) 0173 , 0174 #endif 0175 _isStopped(false) 0176 { 0177 // Debug unittest on CI 0178 if (qAppName() == QLatin1String("kservicetest") || qAppName() == QLatin1String("filetypestest")) { 0179 s_verboseDebug = true; 0180 } 0181 m_statRescanTimer.setObjectName(QStringLiteral("KDirWatchPrivate::timer")); 0182 connect(&m_statRescanTimer, &QTimer::timeout, this, &KDirWatchPrivate::slotRescan); 0183 0184 m_nfsPollInterval = qEnvironmentVariableIsSet(s_envNfsPoll) ? qEnvironmentVariableIntValue(s_envNfsPoll) : 5000; 0185 m_PollInterval = qEnvironmentVariableIsSet(s_envPoll) ? qEnvironmentVariableIntValue(s_envPoll) : 500; 0186 0187 m_preferredMethod = methodFromString(qEnvironmentVariableIsSet(s_envMethod) ? qgetenv(s_envMethod) : "inotify"); 0188 // The nfs method defaults to the normal (local) method 0189 m_nfsPreferredMethod = methodFromString(qEnvironmentVariableIsSet(s_envNfsMethod) ? qgetenv(s_envNfsMethod) : "Stat"); 0190 0191 QList<QByteArray> availableMethods; 0192 0193 availableMethods << "Stat"; 0194 0195 // used for inotify 0196 rescan_timer.setObjectName(QStringLiteral("KDirWatchPrivate::rescan_timer")); 0197 rescan_timer.setSingleShot(true); 0198 connect(&rescan_timer, &QTimer::timeout, this, &KDirWatchPrivate::slotRescan); 0199 0200 #if HAVE_SYS_INOTIFY_H 0201 m_inotify_fd = inotify_init(); 0202 supports_inotify = m_inotify_fd > 0; 0203 0204 if (!supports_inotify) { 0205 qCDebug(KDIRWATCH) << "Can't use Inotify, kernel doesn't support it:" << strerror(errno); 0206 } else { 0207 availableMethods << "INotify"; 0208 (void)fcntl(m_inotify_fd, F_SETFD, FD_CLOEXEC); 0209 0210 mSn = new QSocketNotifier(m_inotify_fd, QSocketNotifier::Read, this); 0211 connect(mSn, &QSocketNotifier::activated, this, &KDirWatchPrivate::inotifyEventReceived); 0212 } 0213 #endif 0214 #if HAVE_QFILESYSTEMWATCHER 0215 availableMethods << "QFileSystemWatcher"; 0216 fsWatcher = nullptr; 0217 #endif 0218 0219 qCDebug(KDIRWATCH) << "Available methods: " << availableMethods << "preferred=" << methodToString(m_preferredMethod); 0220 } 0221 0222 // This is called on app exit (deleted by QThreadStorage) 0223 KDirWatchPrivate::~KDirWatchPrivate() 0224 { 0225 m_statRescanTimer.stop(); 0226 0227 // Unset us as d pointer. This indicates to the KDirWatch that the private has already been destroyed and therefore 0228 // needs no additional cleanup from its destructor. 0229 for (auto it = m_mapEntries.begin(); it != m_mapEntries.end(); it++) { 0230 auto &entry = it.value(); 0231 for (auto &client : entry.m_clients) { 0232 client.instance->d = nullptr; 0233 } 0234 } 0235 // Also cover all instance that hold a reference to us (even when they don't have an entry) 0236 for (auto &referenceObject : m_referencesObjects) { 0237 referenceObject->d = nullptr; 0238 } 0239 0240 #if HAVE_SYS_INOTIFY_H 0241 if (supports_inotify) { 0242 QT_CLOSE(m_inotify_fd); 0243 } 0244 #endif 0245 #if HAVE_QFILESYSTEMWATCHER 0246 delete fsWatcher; 0247 #endif 0248 } 0249 0250 void KDirWatchPrivate::inotifyEventReceived() 0251 { 0252 #if HAVE_SYS_INOTIFY_H 0253 if (!supports_inotify) { 0254 return; 0255 } 0256 0257 int pending = -1; 0258 int offsetStartRead = 0; // where we read into buffer 0259 char buf[8192]; 0260 assert(m_inotify_fd > -1); 0261 ioctl(m_inotify_fd, FIONREAD, &pending); 0262 0263 while (pending > 0) { 0264 const int bytesToRead = qMin<int>(pending, sizeof(buf) - offsetStartRead); 0265 0266 int bytesAvailable = read(m_inotify_fd, &buf[offsetStartRead], bytesToRead); 0267 pending -= bytesAvailable; 0268 bytesAvailable += offsetStartRead; 0269 offsetStartRead = 0; 0270 0271 int offsetCurrent = 0; 0272 while (bytesAvailable >= int(sizeof(struct inotify_event))) { 0273 const struct inotify_event *const event = reinterpret_cast<inotify_event *>(&buf[offsetCurrent]); 0274 0275 if (event->mask & IN_Q_OVERFLOW) { 0276 qCWarning(KDIRWATCH) << "Inotify Event queue overflowed, check max_queued_events value"; 0277 return; 0278 } 0279 0280 const int eventSize = sizeof(struct inotify_event) + event->len; 0281 if (bytesAvailable < eventSize) { 0282 break; 0283 } 0284 0285 bytesAvailable -= eventSize; 0286 offsetCurrent += eventSize; 0287 0288 QString path; 0289 // strip trailing null chars, see inotify_event documentation 0290 // these must not end up in the final QString version of path 0291 int len = event->len; 0292 while (len > 1 && !event->name[len - 1]) { 0293 --len; 0294 } 0295 QByteArray cpath(event->name, len); 0296 if (len) { 0297 path = QFile::decodeName(cpath); 0298 } 0299 0300 if (!path.isEmpty() && isNoisyFile(cpath.data())) { 0301 continue; 0302 } 0303 0304 // Is set to true if the new event is a directory, false otherwise. This prevents a stat call in clientsForFileOrDir 0305 const bool isDir = (event->mask & (IN_ISDIR)); 0306 0307 Entry *e = m_inotify_wd_to_entry.value(event->wd); 0308 if (!e) { 0309 continue; 0310 } 0311 const bool wasDirty = e->dirty; 0312 e->dirty = true; 0313 0314 const QString tpath = e->path + QLatin1Char('/') + path; 0315 0316 qCDebug(KDIRWATCH).nospace() << "got event " << inotifyEventName(event) << " for entry " << e->path 0317 << (event->mask & IN_ISDIR ? " [directory] " : " [file] ") << path; 0318 0319 if (event->mask & IN_DELETE_SELF) { 0320 e->m_status = NonExistent; 0321 m_inotify_wd_to_entry.remove(e->wd); 0322 e->wd = -1; 0323 e->m_ctime = invalid_ctime; 0324 emitEvent(e, Deleted); 0325 // If the parent dir was already watched, tell it something changed 0326 Entry *parentEntry = entry(e->parentDirectory()); 0327 if (parentEntry) { 0328 parentEntry->dirty = true; 0329 } 0330 // Add entry to parent dir to notice if the entry gets recreated 0331 addEntry(nullptr, e->parentDirectory(), e, true /*isDir*/); 0332 } 0333 if (event->mask & IN_IGNORED) { 0334 // Causes bug #207361 with kernels 2.6.31 and 2.6.32! 0335 // e->wd = -1; 0336 } 0337 if (event->mask & (IN_CREATE | IN_MOVED_TO)) { 0338 Entry *sub_entry = e->findSubEntry(tpath); 0339 0340 qCDebug(KDIRWATCH) << "-->got CREATE signal for" << (tpath) << "sub_entry=" << sub_entry; 0341 0342 if (sub_entry) { 0343 // We were waiting for this new file/dir to be created 0344 sub_entry->dirty = true; 0345 rescan_timer.start(0); // process this asap, to start watching that dir 0346 } else if (e->isDir && !e->m_clients.empty()) { 0347 const QList<const Client *> clients = e->inotifyClientsForFileOrDir(isDir); 0348 // See discussion in addEntry for why we don't addEntry for individual 0349 // files in WatchFiles mode with inotify. 0350 if (isDir) { 0351 for (const Client *client : clients) { 0352 addEntry(client->instance, tpath, nullptr, isDir, isDir ? client->m_watchModes : KDirWatch::WatchDirOnly); 0353 } 0354 } 0355 if (!clients.isEmpty()) { 0356 emitEvent(e, Created, tpath); 0357 qCDebug(KDIRWATCH).nospace() << clients.count() << " instance(s) monitoring the new " << (isDir ? "dir " : "file ") << tpath; 0358 } 0359 e->m_pendingFileChanges.append(e->path); 0360 if (!rescan_timer.isActive()) { 0361 rescan_timer.start(m_PollInterval); // singleshot 0362 } 0363 } 0364 } 0365 if (event->mask & (IN_DELETE | IN_MOVED_FROM)) { 0366 if ((e->isDir) && (!e->m_clients.empty())) { 0367 // A file in this directory has been removed. It wasn't an explicitly 0368 // watched file as it would have its own watch descriptor, so 0369 // no addEntry/ removeEntry bookkeeping should be required. Emit 0370 // the event immediately if any clients are interested. 0371 const KDirWatch::WatchModes flag = isDir ? KDirWatch::WatchSubDirs : KDirWatch::WatchFiles; 0372 int counter = std::count_if(e->m_clients.cbegin(), e->m_clients.cend(), [flag](const Client &client) { 0373 return client.m_watchModes & flag; 0374 }); 0375 0376 if (counter != 0) { 0377 emitEvent(e, Deleted, tpath); 0378 } 0379 } 0380 } 0381 if (event->mask & (IN_MODIFY | IN_ATTRIB)) { 0382 if ((e->isDir) && (!e->m_clients.empty())) { 0383 // A file in this directory has been changed. No 0384 // addEntry/ removeEntry bookkeeping should be required. 0385 // Add the path to the list of pending file changes if 0386 // there are any interested clients. 0387 // QT_STATBUF stat_buf; 0388 // QByteArray tpath = QFile::encodeName(e->path+'/'+path); 0389 // QT_STAT(tpath, &stat_buf); 0390 // bool isDir = S_ISDIR(stat_buf.st_mode); 0391 0392 // The API doc is somewhat vague as to whether we should emit 0393 // dirty() for implicitly watched files when WatchFiles has 0394 // not been specified - we'll assume they are always interested, 0395 // regardless. 0396 // Don't worry about duplicates for the time 0397 // being; this is handled in slotRescan. 0398 e->m_pendingFileChanges.append(tpath); 0399 // Avoid stat'ing the directory if only an entry inside it changed. 0400 e->dirty = (wasDirty || (path.isEmpty() && (event->mask & IN_ATTRIB))); 0401 } 0402 } 0403 0404 if (!rescan_timer.isActive()) { 0405 rescan_timer.start(m_PollInterval); // singleshot 0406 } 0407 } 0408 if (bytesAvailable > 0) { 0409 // copy partial event to beginning of buffer 0410 memmove(buf, &buf[offsetCurrent], bytesAvailable); 0411 offsetStartRead = bytesAvailable; 0412 } 0413 } 0414 #endif 0415 } 0416 0417 KDirWatchPrivate::Entry::~Entry() 0418 { 0419 } 0420 0421 /* In inotify mode, only entries which are marked dirty are scanned. 0422 * We first need to mark all yet nonexistent, but possible created 0423 * entries as dirty... 0424 */ 0425 void KDirWatchPrivate::Entry::propagate_dirty() 0426 { 0427 for (Entry *sub_entry : std::as_const(m_entries)) { 0428 if (!sub_entry->dirty) { 0429 sub_entry->dirty = true; 0430 sub_entry->propagate_dirty(); 0431 } 0432 } 0433 } 0434 0435 /* A KDirWatch instance is interested in getting events for 0436 * this file/Dir entry. 0437 */ 0438 void KDirWatchPrivate::Entry::addClient(KDirWatch *instance, KDirWatch::WatchModes watchModes) 0439 { 0440 if (instance == nullptr) { 0441 return; 0442 } 0443 0444 auto it = findInstance(instance); 0445 if (it != m_clients.end()) { 0446 Client &client = *it; 0447 ++client.count; 0448 client.m_watchModes = watchModes; 0449 return; 0450 } 0451 0452 m_clients.emplace_back(instance, watchModes); 0453 } 0454 0455 void KDirWatchPrivate::Entry::removeClient(KDirWatch *instance) 0456 { 0457 auto it = findInstance(instance); 0458 if (it != m_clients.end()) { 0459 Client &client = *it; 0460 --client.count; 0461 if (client.count == 0) { 0462 m_clients.erase(it); 0463 } 0464 } 0465 } 0466 0467 /* get number of clients */ 0468 int KDirWatchPrivate::Entry::clientCount() const 0469 { 0470 int clients = 0; 0471 for (const Client &client : m_clients) { 0472 clients += client.count; 0473 } 0474 0475 return clients; 0476 } 0477 0478 QString KDirWatchPrivate::Entry::parentDirectory() const 0479 { 0480 return QDir::cleanPath(path + QLatin1String("/..")); 0481 } 0482 0483 QList<const KDirWatchPrivate::Client *> KDirWatchPrivate::Entry::clientsForFileOrDir(const QString &tpath, bool *isDir) const 0484 { 0485 QList<const Client *> ret; 0486 QFileInfo fi(tpath); 0487 if (fi.exists()) { 0488 *isDir = fi.isDir(); 0489 const KDirWatch::WatchModes flag = *isDir ? KDirWatch::WatchSubDirs : KDirWatch::WatchFiles; 0490 for (const Client &client : m_clients) { 0491 if (client.m_watchModes & flag) { 0492 ret.append(&client); 0493 } 0494 } 0495 } else { 0496 // Happens frequently, e.g. ERROR: couldn't stat "/home/dfaure/.viminfo.tmp" 0497 // qCDebug(KDIRWATCH) << "ERROR: couldn't stat" << tpath; 0498 // In this case isDir is not set, but ret is empty anyway 0499 // so isDir won't be used. 0500 } 0501 return ret; 0502 } 0503 0504 // inotify specific function that doesn't call KDE::stat to figure out if we have a file or folder. 0505 // isDir is determined through inotify's "IN_ISDIR" flag in KDirWatchPrivate::inotifyEventReceived 0506 QList<const KDirWatchPrivate::Client *> KDirWatchPrivate::Entry::inotifyClientsForFileOrDir(bool isDir) const 0507 { 0508 QList<const Client *> ret; 0509 const KDirWatch::WatchModes flag = isDir ? KDirWatch::WatchSubDirs : KDirWatch::WatchFiles; 0510 for (const Client &client : m_clients) { 0511 if (client.m_watchModes & flag) { 0512 ret.append(&client); 0513 } 0514 } 0515 return ret; 0516 } 0517 0518 QDebug operator<<(QDebug debug, const KDirWatch & /* watch */) 0519 { 0520 if (!dwp_self.hasLocalData()) { 0521 debug << "KDirWatch not used"; 0522 return debug; 0523 } 0524 debug << dwp_self.localData(); 0525 return debug; 0526 } 0527 0528 QDebug operator<<(QDebug debug, const KDirWatchPrivate &dwp) 0529 { 0530 debug << "Entries watched:"; 0531 if (dwp.m_mapEntries.count() == 0) { 0532 debug << " None."; 0533 } else { 0534 auto it = dwp.m_mapEntries.cbegin(); 0535 for (; it != dwp.m_mapEntries.cend(); ++it) { 0536 const KDirWatchPrivate::Entry &e = it.value(); 0537 debug << " " << e; 0538 0539 for (const KDirWatchPrivate::Client &c : e.m_clients) { 0540 QByteArray pending; 0541 if (c.watchingStopped) { 0542 if (c.pending & KDirWatchPrivate::Deleted) { 0543 pending += "deleted "; 0544 } 0545 if (c.pending & KDirWatchPrivate::Created) { 0546 pending += "created "; 0547 } 0548 if (c.pending & KDirWatchPrivate::Changed) { 0549 pending += "changed "; 0550 } 0551 if (!pending.isEmpty()) { 0552 pending = " (pending: " + pending + ')'; 0553 } 0554 pending = ", stopped" + pending; 0555 } 0556 debug << " by " << c.instance->objectName() << " (" << c.count << " times)" << pending; 0557 } 0558 if (!e.m_entries.isEmpty()) { 0559 debug << " dependent entries:"; 0560 for (KDirWatchPrivate::Entry *d : e.m_entries) { 0561 debug << " " << d << d->path << (d->m_status == KDirWatchPrivate::NonExistent ? "NonExistent" : "EXISTS this is an ERROR!"); 0562 if (s_verboseDebug) { 0563 Q_ASSERT(d->m_status == KDirWatchPrivate::NonExistent); // it doesn't belong here otherwise 0564 } 0565 } 0566 } 0567 } 0568 } 0569 return debug; 0570 } 0571 0572 QDebug operator<<(QDebug debug, const KDirWatchPrivate::Entry &entry) 0573 { 0574 debug.nospace() << "[ Entry for " << entry.path << ", " << (entry.isDir ? "dir" : "file"); 0575 if (entry.m_status == KDirWatchPrivate::NonExistent) { 0576 debug << ", non-existent"; 0577 } 0578 debug << ", using " 0579 << ((entry.m_mode == KDirWatchPrivate::INotifyMode) ? "INotify" 0580 : (entry.m_mode == KDirWatchPrivate::QFSWatchMode) ? "QFSWatch" 0581 : (entry.m_mode == KDirWatchPrivate::StatMode) ? "Stat" 0582 : "Unknown Method"); 0583 #if HAVE_SYS_INOTIFY_H 0584 if (entry.m_mode == KDirWatchPrivate::INotifyMode) { 0585 debug << " inotify_wd=" << entry.wd; 0586 } 0587 #endif 0588 debug << ", has " << entry.m_clients.size() << " clients"; 0589 debug.space(); 0590 if (!entry.m_entries.isEmpty()) { 0591 debug << ", nonexistent subentries:"; 0592 for (KDirWatchPrivate::Entry *subEntry : std::as_const(entry.m_entries)) { 0593 debug << subEntry << subEntry->path; 0594 } 0595 } 0596 debug << ']'; 0597 return debug; 0598 } 0599 0600 KDirWatchPrivate::Entry *KDirWatchPrivate::entry(const QString &_path) 0601 { 0602 if (_path.isEmpty()) { 0603 return nullptr; 0604 } 0605 0606 QString path(_path); 0607 0608 if (path.length() > 1 && path.endsWith(QLatin1Char('/'))) { 0609 path.chop(1); 0610 } 0611 0612 auto it = m_mapEntries.find(path); 0613 return it != m_mapEntries.end() ? &it.value() : nullptr; 0614 } 0615 0616 // set polling frequency for a entry and adjust global freq if needed 0617 void KDirWatchPrivate::useFreq(Entry *e, int newFreq) 0618 { 0619 e->freq = newFreq; 0620 0621 // a reasonable frequency for the global polling timer 0622 if (e->freq < freq) { 0623 freq = e->freq; 0624 if (m_statRescanTimer.isActive()) { 0625 m_statRescanTimer.start(freq); 0626 } 0627 qCDebug(KDIRWATCH) << "Global Poll Freq is now" << freq << "msec"; 0628 } 0629 } 0630 0631 #if HAVE_SYS_INOTIFY_H 0632 // setup INotify notification, returns false if not possible 0633 bool KDirWatchPrivate::useINotify(Entry *e) 0634 { 0635 e->wd = -1; 0636 e->dirty = false; 0637 0638 if (!supports_inotify) { 0639 return false; 0640 } 0641 0642 e->m_mode = INotifyMode; 0643 0644 if (e->m_status == NonExistent) { 0645 addEntry(nullptr, e->parentDirectory(), e, true); 0646 return true; 0647 } 0648 0649 // May as well register for almost everything - it's free! 0650 int mask = IN_DELETE | IN_DELETE_SELF | IN_CREATE | IN_MOVE | IN_MOVE_SELF | IN_DONT_FOLLOW | IN_MOVED_FROM | IN_MODIFY | IN_ATTRIB; 0651 0652 if ((e->wd = inotify_add_watch(m_inotify_fd, QFile::encodeName(e->path).data(), mask)) != -1) { 0653 m_inotify_wd_to_entry.insert(e->wd, e); 0654 if (s_verboseDebug) { 0655 qCDebug(KDIRWATCH) << "inotify successfully used for monitoring" << e->path << "wd=" << e->wd; 0656 } 0657 return true; 0658 } 0659 0660 if (errno == ENOSPC) { 0661 // Inotify max_user_watches was reached (/proc/sys/fs/inotify/max_user_watches) 0662 // See man inotify_add_watch, https://github.com/guard/listen/wiki/Increasing-the-amount-of-inotify-watchers 0663 qCWarning(KDIRWATCH) << "inotify failed for monitoring" << e->path << "\n" 0664 << "Because it reached its max_user_watches,\n" 0665 << "you can increase the maximum number of file watches per user,\n" 0666 << "by setting an appropriate fs.inotify.max_user_watches parameter in your /etc/sysctl.conf"; 0667 } else { 0668 qCDebug(KDIRWATCH) << "inotify failed for monitoring" << e->path << ":" << strerror(errno) << " (errno:" << errno << ")"; 0669 } 0670 return false; 0671 } 0672 #endif 0673 #if HAVE_QFILESYSTEMWATCHER 0674 bool KDirWatchPrivate::useQFSWatch(Entry *e) 0675 { 0676 e->m_mode = QFSWatchMode; 0677 e->dirty = false; 0678 0679 if (e->m_status == NonExistent) { 0680 addEntry(nullptr, e->parentDirectory(), e, true /*isDir*/); 0681 return true; 0682 } 0683 0684 // qCDebug(KDIRWATCH) << "fsWatcher->addPath" << e->path; 0685 if (!fsWatcher) { 0686 fsWatcher = new QFileSystemWatcher(); 0687 connect(fsWatcher, &QFileSystemWatcher::directoryChanged, this, &KDirWatchPrivate::fswEventReceived); 0688 connect(fsWatcher, &QFileSystemWatcher::fileChanged, this, &KDirWatchPrivate::fswEventReceived); 0689 } 0690 fsWatcher->addPath(e->path); 0691 return true; 0692 } 0693 #endif 0694 0695 bool KDirWatchPrivate::useStat(Entry *e) 0696 { 0697 if (KFileSystemType::fileSystemType(e->path) == KFileSystemType::Nfs) { // TODO: or Smbfs? 0698 useFreq(e, m_nfsPollInterval); 0699 } else { 0700 useFreq(e, m_PollInterval); 0701 } 0702 0703 if (e->m_mode != StatMode) { 0704 e->m_mode = StatMode; 0705 statEntries++; 0706 0707 if (statEntries == 1) { 0708 // if this was first STAT entry (=timer was stopped) 0709 m_statRescanTimer.start(freq); // then start the timer 0710 qCDebug(KDIRWATCH) << " Started Polling Timer, freq " << freq; 0711 } 0712 } 0713 0714 qCDebug(KDIRWATCH) << " Setup Stat (freq " << e->freq << ") for " << e->path; 0715 0716 return true; 0717 } 0718 0719 /* If <instance> !=0, this KDirWatch instance wants to watch at <_path>, 0720 * providing in <isDir> the type of the entry to be watched. 0721 * Sometimes, entries are dependent on each other: if <sub_entry> !=0, 0722 * this entry needs another entry to watch itself (when notExistent). 0723 */ 0724 void KDirWatchPrivate::addEntry(KDirWatch *instance, const QString &_path, Entry *sub_entry, bool isDir, KDirWatch::WatchModes watchModes) 0725 { 0726 QString path(_path); 0727 if (path.startsWith(QLatin1String(":/"))) { 0728 qCWarning(KDIRWATCH) << "Cannot watch QRC-like path" << path; 0729 return; 0730 } 0731 if (path.isEmpty() 0732 #ifndef Q_OS_WIN 0733 || path == QLatin1String("/dev") 0734 || (path.startsWith(QLatin1String("/dev/")) && !path.startsWith(QLatin1String("/dev/.")) && !path.startsWith(QLatin1String("/dev/shm"))) 0735 #endif 0736 ) { 0737 return; // Don't even go there. 0738 } 0739 0740 if (path.length() > 1 && path.endsWith(QLatin1Char('/'))) { 0741 path.chop(1); 0742 } 0743 0744 auto it = m_mapEntries.find(path); 0745 if (it != m_mapEntries.end()) { 0746 Entry &entry = it.value(); 0747 if (sub_entry) { 0748 entry.m_entries.append(sub_entry); 0749 if (s_verboseDebug) { 0750 qCDebug(KDIRWATCH) << "Added already watched Entry" << path << "(for" << sub_entry->path << ")"; 0751 } 0752 } else { 0753 entry.addClient(instance, watchModes); 0754 if (s_verboseDebug) { 0755 qCDebug(KDIRWATCH) << "Added already watched Entry" << path << "(now" << entry.clientCount() << "clients)" 0756 << QStringLiteral("[%1]").arg(instance->objectName()); 0757 } 0758 } 0759 return; 0760 } 0761 0762 // we have a new path to watch 0763 0764 QT_STATBUF stat_buf; 0765 bool exists = (QT_STAT(QFile::encodeName(path).constData(), &stat_buf) == 0); 0766 0767 auto newIt = m_mapEntries.insert(path, Entry()); 0768 // the insert does a copy, so we have to use <e> now 0769 Entry *e = &(*newIt); 0770 0771 if (exists) { 0772 e->isDir = (stat_buf.st_mode & QT_STAT_MASK) == QT_STAT_DIR; 0773 0774 #ifndef Q_OS_WIN 0775 if (e->isDir && !isDir) { 0776 if (QT_LSTAT(QFile::encodeName(path).constData(), &stat_buf) == 0) { 0777 if ((stat_buf.st_mode & QT_STAT_MASK) == QT_STAT_LNK) { 0778 // if it's a symlink, don't follow it 0779 e->isDir = false; 0780 } 0781 } 0782 } 0783 #endif 0784 0785 if (e->isDir && !isDir) { 0786 qCWarning(KCOREADDONS_DEBUG) << "KDirWatch:" << path << "is a directory. Use addDir!"; 0787 } else if (!e->isDir && isDir) { 0788 qCWarning(KCOREADDONS_DEBUG) << "KDirWatch:" << path << "is a file. Use addFile!"; 0789 } 0790 0791 if (!e->isDir && (watchModes != KDirWatch::WatchDirOnly)) { 0792 qCWarning(KCOREADDONS_DEBUG) << "KDirWatch:" << path 0793 << "is a file. You can't use recursive or " 0794 "watchFiles options"; 0795 watchModes = KDirWatch::WatchDirOnly; 0796 } 0797 0798 #ifdef Q_OS_WIN 0799 // ctime is the 'creation time' on windows - use mtime instead 0800 e->m_ctime = stat_buf.st_mtime; 0801 #else 0802 e->m_ctime = stat_buf.st_ctime; 0803 #endif 0804 e->m_status = Normal; 0805 e->m_nlink = stat_buf.st_nlink; 0806 e->m_ino = stat_buf.st_ino; 0807 } else { 0808 e->isDir = isDir; 0809 e->m_ctime = invalid_ctime; 0810 e->m_status = NonExistent; 0811 e->m_nlink = 0; 0812 e->m_ino = 0; 0813 } 0814 0815 e->path = path; 0816 if (sub_entry) { 0817 e->m_entries.append(sub_entry); 0818 } else { 0819 e->addClient(instance, watchModes); 0820 } 0821 0822 if (s_verboseDebug) { 0823 qCDebug(KDIRWATCH).nospace() << "Added " << (e->isDir ? "Dir " : "File ") << path << (e->m_status == NonExistent ? " NotExisting" : "") << " for " 0824 << (sub_entry ? sub_entry->path : QString()) << " [" << (instance ? instance->objectName() : QString()) << "]"; 0825 } 0826 0827 // now setup the notification method 0828 e->m_mode = UnknownMode; 0829 e->msecLeft = 0; 0830 0831 if (isNoisyFile(QFile::encodeName(path).data())) { 0832 return; 0833 } 0834 0835 if (exists && e->isDir && (watchModes != KDirWatch::WatchDirOnly)) { 0836 // recursive watch for folders 0837 QFlags<QDir::Filter> filters = QDir::NoDotAndDotDot; 0838 0839 if ((watchModes & KDirWatch::WatchSubDirs) && (watchModes & KDirWatch::WatchFiles)) { 0840 filters |= (QDir::Dirs | QDir::Files); 0841 } else if (watchModes & KDirWatch::WatchSubDirs) { 0842 filters |= QDir::Dirs; 0843 } else if (watchModes & KDirWatch::WatchFiles) { 0844 filters |= QDir::Files; 0845 } 0846 0847 #if HAVE_SYS_INOTIFY_H 0848 if (m_preferredMethod == KDirWatch::INotify) { 0849 // qCDebug(KDIRWATCH) << "Ignoring WatchFiles directive - this is implicit with inotify"; 0850 // Placing a watch on individual files is redundant with inotify 0851 // (inotify gives us WatchFiles functionality "for free") and indeed 0852 // actively harmful, so prevent it. WatchSubDirs is necessary, though. 0853 filters &= ~QDir::Files; 0854 } 0855 #endif 0856 0857 QDir basedir(e->path); 0858 const QFileInfoList contents = basedir.entryInfoList(filters); 0859 for (const QFileInfo &fileInfo : contents) { 0860 // treat symlinks as files--don't follow them. 0861 bool isDir = fileInfo.isDir() && !fileInfo.isSymLink(); 0862 0863 addEntry(instance, fileInfo.absoluteFilePath(), nullptr, isDir, isDir ? watchModes : KDirWatch::WatchDirOnly); 0864 } 0865 } 0866 0867 addWatch(e); 0868 } 0869 0870 void KDirWatchPrivate::addWatch(Entry *e) 0871 { 0872 // If the watch is on a network filesystem use the nfsPreferredMethod as the 0873 // default, otherwise use preferredMethod as the default, if the methods are 0874 // the same we can skip the mountpoint check 0875 0876 // This allows to configure a different method for NFS mounts, since inotify 0877 // cannot detect changes made by other machines. However as a default inotify 0878 // is fine, since the most common case is a NFS-mounted home, where all changes 0879 // are made locally. #177892. 0880 0881 KDirWatch::Method preferredMethod = m_preferredMethod; 0882 if (m_nfsPreferredMethod != m_preferredMethod) { 0883 if (KFileSystemType::fileSystemType(e->path) == KFileSystemType::Nfs) { 0884 preferredMethod = m_nfsPreferredMethod; 0885 } 0886 } 0887 0888 // Try the appropriate preferred method from the config first 0889 bool entryAdded = false; 0890 switch (preferredMethod) { 0891 #if HAVE_SYS_INOTIFY_H 0892 case KDirWatch::INotify: 0893 entryAdded = useINotify(e); 0894 break; 0895 #else 0896 case KDirWatch::INotify: 0897 entryAdded = false; 0898 break; 0899 #endif 0900 #if HAVE_QFILESYSTEMWATCHER 0901 case KDirWatch::QFSWatch: 0902 entryAdded = useQFSWatch(e); 0903 break; 0904 #else 0905 case KDirWatch::QFSWatch: 0906 entryAdded = false; 0907 break; 0908 #endif 0909 case KDirWatch::Stat: 0910 entryAdded = useStat(e); 0911 break; 0912 } 0913 0914 // Failing that try in order INotify, QFSWatch, Stat 0915 if (!entryAdded) { 0916 #if HAVE_SYS_INOTIFY_H 0917 if (preferredMethod != KDirWatch::INotify && useINotify(e)) { 0918 return; 0919 } 0920 #endif 0921 #if HAVE_QFILESYSTEMWATCHER 0922 if (preferredMethod != KDirWatch::QFSWatch && useQFSWatch(e)) { 0923 return; 0924 } 0925 #endif 0926 if (preferredMethod != KDirWatch::Stat) { 0927 useStat(e); 0928 } 0929 } 0930 } 0931 0932 void KDirWatchPrivate::removeWatch(Entry *e) 0933 { 0934 #if HAVE_SYS_INOTIFY_H 0935 if (e->m_mode == INotifyMode) { 0936 m_inotify_wd_to_entry.remove(e->wd); 0937 (void)inotify_rm_watch(m_inotify_fd, e->wd); 0938 if (s_verboseDebug) { 0939 qCDebug(KDIRWATCH).nospace() << "Cancelled INotify (fd " << m_inotify_fd << ", " << e->wd << ") for " << e->path; 0940 } 0941 } 0942 #endif 0943 #if HAVE_QFILESYSTEMWATCHER 0944 if (e->m_mode == QFSWatchMode && fsWatcher) { 0945 if (s_verboseDebug) { 0946 qCDebug(KDIRWATCH) << "fsWatcher->removePath" << e->path; 0947 } 0948 fsWatcher->removePath(e->path); 0949 } 0950 #endif 0951 } 0952 0953 void KDirWatchPrivate::removeEntry(KDirWatch *instance, const QString &_path, Entry *sub_entry) 0954 { 0955 qCDebug(KDIRWATCH) << "path=" << _path << "sub_entry:" << sub_entry; 0956 0957 Entry *e = entry(_path); 0958 if (e) { 0959 removeEntry(instance, e, sub_entry); 0960 } 0961 } 0962 0963 void KDirWatchPrivate::removeEntry(KDirWatch *instance, Entry *e, Entry *sub_entry) 0964 { 0965 removeList.remove(e); 0966 0967 if (sub_entry) { 0968 e->m_entries.removeAll(sub_entry); 0969 } else { 0970 e->removeClient(instance); 0971 } 0972 0973 if (!e->m_clients.empty() || !e->m_entries.empty()) { 0974 return; 0975 } 0976 0977 if (delayRemove) { 0978 removeList.insert(e); 0979 // now e->isValid() is false 0980 return; 0981 } 0982 0983 if (e->m_status == Normal) { 0984 removeWatch(e); 0985 } else { 0986 // Removed a NonExistent entry - we just remove it from the parent 0987 if (e->isDir) { 0988 removeEntry(nullptr, e->parentDirectory(), e); 0989 } else { 0990 removeEntry(nullptr, QFileInfo(e->path).absolutePath(), e); 0991 } 0992 } 0993 0994 if (e->m_mode == StatMode) { 0995 statEntries--; 0996 if (statEntries == 0) { 0997 m_statRescanTimer.stop(); // stop timer if lists are empty 0998 qCDebug(KDIRWATCH) << " Stopped Polling Timer"; 0999 } 1000 } 1001 1002 if (s_verboseDebug) { 1003 qCDebug(KDIRWATCH).nospace() << "Removed " << (e->isDir ? "Dir " : "File ") << e->path << " for " << (sub_entry ? sub_entry->path : QString()) << " [" 1004 << (instance ? instance->objectName() : QString()) << "]"; 1005 } 1006 QString p = e->path; // take a copy, QMap::remove takes a reference and deletes, since e points into the map 1007 #if HAVE_SYS_INOTIFY_H 1008 m_inotify_wd_to_entry.remove(e->wd); 1009 #endif 1010 m_mapEntries.remove(p); // <e> not valid any more 1011 } 1012 1013 /* Called from KDirWatch destructor: 1014 * remove <instance> as client from all entries 1015 */ 1016 void KDirWatchPrivate::removeEntries(KDirWatch *instance) 1017 { 1018 int minfreq = 3600000; 1019 1020 QStringList pathList; 1021 // put all entries where instance is a client in list 1022 for (auto it = m_mapEntries.begin(); it != m_mapEntries.end(); ++it) { 1023 Entry &entry = it.value(); 1024 auto clientIt = entry.findInstance(instance); 1025 if (clientIt != entry.m_clients.end()) { 1026 clientIt->count = 1; // forces deletion of instance as client 1027 pathList.append(entry.path); 1028 } else if (entry.m_mode == StatMode && entry.freq < minfreq) { 1029 minfreq = entry.freq; 1030 } 1031 } 1032 1033 for (const QString &path : std::as_const(pathList)) { 1034 removeEntry(instance, path, nullptr); 1035 } 1036 1037 if (minfreq > freq) { 1038 // we can decrease the global polling frequency 1039 freq = minfreq; 1040 if (m_statRescanTimer.isActive()) { 1041 m_statRescanTimer.start(freq); 1042 } 1043 qCDebug(KDIRWATCH) << "Poll Freq now" << freq << "msec"; 1044 } 1045 } 1046 1047 // instance ==0: stop scanning for all instances 1048 bool KDirWatchPrivate::stopEntryScan(KDirWatch *instance, Entry *e) 1049 { 1050 int stillWatching = 0; 1051 for (Client &client : e->m_clients) { 1052 if (!instance || instance == client.instance) { 1053 client.watchingStopped = true; 1054 } else if (!client.watchingStopped) { 1055 stillWatching += client.count; 1056 } 1057 } 1058 1059 qCDebug(KDIRWATCH) << (instance ? instance->objectName() : QStringLiteral("all")) << "stopped scanning" << e->path << "(now" << stillWatching 1060 << "watchers)"; 1061 1062 if (stillWatching == 0) { 1063 // if nobody is interested, we don't watch, and we don't report 1064 // changes that happened while not watching 1065 e->m_ctime = invalid_ctime; // invalid 1066 1067 // Changing m_status like this would create wrong "created" events in stat mode. 1068 // To really "stop watching" we would need to determine 'stillWatching==0' in scanEntry... 1069 // e->m_status = NonExistent; 1070 } 1071 return true; 1072 } 1073 1074 // instance ==0: start scanning for all instances 1075 bool KDirWatchPrivate::restartEntryScan(KDirWatch *instance, Entry *e, bool notify) 1076 { 1077 int wasWatching = 0; 1078 int newWatching = 0; 1079 for (Client &client : e->m_clients) { 1080 if (!client.watchingStopped) { 1081 wasWatching += client.count; 1082 } else if (!instance || instance == client.instance) { 1083 client.watchingStopped = false; 1084 newWatching += client.count; 1085 } 1086 } 1087 if (newWatching == 0) { 1088 return false; 1089 } 1090 1091 qCDebug(KDIRWATCH) << (instance ? instance->objectName() : QStringLiteral("all")) << "restarted scanning" << e->path << "(now" << wasWatching + newWatching 1092 << "watchers)"; 1093 1094 // restart watching and emit pending events 1095 1096 int ev = NoChange; 1097 if (wasWatching == 0) { 1098 if (!notify) { 1099 QT_STATBUF stat_buf; 1100 bool exists = (QT_STAT(QFile::encodeName(e->path).constData(), &stat_buf) == 0); 1101 if (exists) { 1102 // ctime is the 'creation time' on windows, but with qMax 1103 // we get the latest change of any kind, on any platform. 1104 e->m_ctime = qMax(stat_buf.st_ctime, stat_buf.st_mtime); 1105 e->m_status = Normal; 1106 if (s_verboseDebug) { 1107 qCDebug(KDIRWATCH) << "Setting status to Normal for" << e << e->path; 1108 } 1109 e->m_nlink = stat_buf.st_nlink; 1110 e->m_ino = stat_buf.st_ino; 1111 1112 // Same as in scanEntry: ensure no subentry in parent dir 1113 removeEntry(nullptr, e->parentDirectory(), e); 1114 } else { 1115 e->m_ctime = invalid_ctime; 1116 e->m_status = NonExistent; 1117 e->m_nlink = 0; 1118 if (s_verboseDebug) { 1119 qCDebug(KDIRWATCH) << "Setting status to NonExistent for" << e << e->path; 1120 } 1121 } 1122 } 1123 e->msecLeft = 0; 1124 ev = scanEntry(e); 1125 } 1126 emitEvent(e, ev); 1127 1128 return true; 1129 } 1130 1131 // instance ==0: stop scanning for all instances 1132 void KDirWatchPrivate::stopScan(KDirWatch *instance) 1133 { 1134 for (auto it = m_mapEntries.begin(); it != m_mapEntries.end(); ++it) { 1135 stopEntryScan(instance, &it.value()); 1136 } 1137 } 1138 1139 void KDirWatchPrivate::startScan(KDirWatch *instance, bool notify, bool skippedToo) 1140 { 1141 if (!notify) { 1142 resetList(instance, skippedToo); 1143 } 1144 1145 for (auto it = m_mapEntries.begin(); it != m_mapEntries.end(); ++it) { 1146 restartEntryScan(instance, &it.value(), notify); 1147 } 1148 1149 // timer should still be running when in polling mode 1150 } 1151 1152 // clear all pending events, also from stopped 1153 void KDirWatchPrivate::resetList(KDirWatch *instance, bool skippedToo) 1154 { 1155 Q_UNUSED(instance); 1156 1157 for (auto it = m_mapEntries.begin(); it != m_mapEntries.end(); ++it) { 1158 for (Client &client : it.value().m_clients) { 1159 if (!client.watchingStopped || skippedToo) { 1160 client.pending = NoChange; 1161 } 1162 } 1163 } 1164 } 1165 1166 // Return event happened on <e> 1167 // 1168 int KDirWatchPrivate::scanEntry(Entry *e) 1169 { 1170 // Shouldn't happen: Ignore "unknown" notification method 1171 if (e->m_mode == UnknownMode) { 1172 return NoChange; 1173 } 1174 1175 if (e->m_mode == INotifyMode) { 1176 // we know nothing has changed, no need to stat 1177 if (!e->dirty) { 1178 return NoChange; 1179 } 1180 e->dirty = false; 1181 } 1182 1183 if (e->m_mode == StatMode) { 1184 // only scan if timeout on entry timer happens; 1185 // e.g. when using 500msec global timer, a entry 1186 // with freq=5000 is only watched every 10th time 1187 1188 e->msecLeft -= freq; 1189 if (e->msecLeft > 0) { 1190 return NoChange; 1191 } 1192 e->msecLeft += e->freq; 1193 } 1194 1195 QT_STATBUF stat_buf; 1196 const bool exists = (QT_STAT(QFile::encodeName(e->path).constData(), &stat_buf) == 0); 1197 if (exists) { 1198 if (e->m_status == NonExistent) { 1199 // ctime is the 'creation time' on windows, but with qMax 1200 // we get the latest change of any kind, on any platform. 1201 e->m_ctime = qMax(stat_buf.st_ctime, stat_buf.st_mtime); 1202 e->m_status = Normal; 1203 e->m_ino = stat_buf.st_ino; 1204 if (s_verboseDebug) { 1205 qCDebug(KDIRWATCH) << "Setting status to Normal for just created" << e << e->path; 1206 } 1207 // We need to make sure the entry isn't listed in its parent's subentries... (#222974, testMoveTo) 1208 removeEntry(nullptr, e->parentDirectory(), e); 1209 1210 return Created; 1211 } 1212 1213 #if 1 // for debugging the if() below 1214 if (s_verboseDebug) { 1215 struct tm *tmp = localtime(&e->m_ctime); 1216 char outstr[200]; 1217 strftime(outstr, sizeof(outstr), "%H:%M:%S", tmp); 1218 qCDebug(KDIRWATCH) << e->path << "e->m_ctime=" << e->m_ctime << outstr << "stat_buf.st_ctime=" << stat_buf.st_ctime 1219 << "stat_buf.st_mtime=" << stat_buf.st_mtime << "e->m_nlink=" << e->m_nlink << "stat_buf.st_nlink=" << stat_buf.st_nlink 1220 << "e->m_ino=" << e->m_ino << "stat_buf.st_ino=" << stat_buf.st_ino; 1221 } 1222 #endif 1223 1224 if ((e->m_ctime != invalid_ctime) 1225 && (qMax(stat_buf.st_ctime, stat_buf.st_mtime) != e->m_ctime || stat_buf.st_ino != e->m_ino 1226 || int(stat_buf.st_nlink) != int(e->m_nlink) 1227 #ifdef Q_OS_WIN 1228 // on Windows, we trust QFSW to get it right, the ctime comparisons above 1229 // fail for example when adding files to directories on Windows 1230 // which doesn't change the mtime of the directory 1231 || e->m_mode == QFSWatchMode 1232 #endif 1233 )) { 1234 e->m_ctime = qMax(stat_buf.st_ctime, stat_buf.st_mtime); 1235 e->m_nlink = stat_buf.st_nlink; 1236 if (e->m_ino != stat_buf.st_ino) { 1237 // The file got deleted and recreated. We need to watch it again. 1238 removeWatch(e); 1239 addWatch(e); 1240 e->m_ino = stat_buf.st_ino; 1241 return (Deleted | Created); 1242 } else { 1243 return Changed; 1244 } 1245 } 1246 1247 return NoChange; 1248 } 1249 1250 // dir/file doesn't exist 1251 1252 e->m_nlink = 0; 1253 e->m_ino = 0; 1254 e->m_status = NonExistent; 1255 1256 if (e->m_ctime == invalid_ctime) { 1257 return NoChange; 1258 } 1259 1260 e->m_ctime = invalid_ctime; 1261 return Deleted; 1262 } 1263 1264 /* Notify all interested KDirWatch instances about a given event on an entry 1265 * and stored pending events. When watching is stopped, the event is 1266 * added to the pending events. 1267 */ 1268 void KDirWatchPrivate::emitEvent(Entry *e, int event, const QString &fileName) 1269 { 1270 QString path(e->path); 1271 if (!fileName.isEmpty()) { 1272 if (!QDir::isRelativePath(fileName)) { 1273 path = fileName; 1274 } else { 1275 #ifdef Q_OS_UNIX 1276 path += QLatin1Char('/') + fileName; 1277 #elif defined(Q_OS_WIN) 1278 // current drive is passed instead of / 1279 path += QStringView(QDir::currentPath()).left(2) + QLatin1Char('/') + fileName; 1280 #endif 1281 } 1282 } 1283 1284 if (s_verboseDebug) { 1285 qCDebug(KDIRWATCH) << event << path << e->m_clients.size() << "clients"; 1286 } 1287 1288 for (Client &c : e->m_clients) { 1289 if (c.instance == nullptr || c.count == 0) { 1290 continue; 1291 } 1292 1293 if (c.watchingStopped) { 1294 // Do not add event to a list of pending events, the docs say restartDirScan won't emit! 1295 continue; 1296 } 1297 // not stopped 1298 if (event == NoChange || event == Changed) { 1299 event |= c.pending; 1300 } 1301 c.pending = NoChange; 1302 if (event == NoChange) { 1303 continue; 1304 } 1305 1306 // Emit the signals delayed, to avoid unexpected re-entrance from the slots (#220153) 1307 1308 if (event & Deleted) { 1309 QMetaObject::invokeMethod( 1310 c.instance, 1311 [c, path]() { 1312 c.instance->setDeleted(path); 1313 }, 1314 Qt::QueuedConnection); 1315 } 1316 1317 if (event & Created) { 1318 QMetaObject::invokeMethod( 1319 c.instance, 1320 [c, path]() { 1321 c.instance->setCreated(path); 1322 }, 1323 Qt::QueuedConnection); 1324 // possible emit Change event after creation 1325 } 1326 1327 if (event & Changed) { 1328 QMetaObject::invokeMethod( 1329 c.instance, 1330 [c, path]() { 1331 c.instance->setDirty(path); 1332 }, 1333 Qt::QueuedConnection); 1334 } 1335 } 1336 } 1337 1338 // Remove entries which were marked to be removed 1339 void KDirWatchPrivate::slotRemoveDelayed() 1340 { 1341 delayRemove = false; 1342 // Removing an entry could also take care of removing its parent 1343 // (e.g. in inotify mode), which would remove other entries in removeList, 1344 // so don't use Q_FOREACH or iterators here... 1345 while (!removeList.isEmpty()) { 1346 Entry *entry = *removeList.begin(); 1347 removeEntry(nullptr, entry, nullptr); // this will remove entry from removeList 1348 } 1349 } 1350 1351 /* Scan all entries to be watched for changes. This is done regularly 1352 * when polling. inotify uses a single-shot timer to call this slot delayed. 1353 */ 1354 void KDirWatchPrivate::slotRescan() 1355 { 1356 if (s_verboseDebug) { 1357 qCDebug(KDIRWATCH); 1358 } 1359 1360 EntryMap::Iterator it; 1361 1362 // People can do very long things in the slot connected to dirty(), 1363 // like showing a message box. We don't want to keep polling during 1364 // that time, otherwise the value of 'delayRemove' will be reset. 1365 // ### TODO: now the emitEvent delays emission, this can be cleaned up 1366 bool timerRunning = m_statRescanTimer.isActive(); 1367 if (timerRunning) { 1368 m_statRescanTimer.stop(); 1369 } 1370 1371 // We delay deletions of entries this way. 1372 // removeDir(), when called in slotDirty(), can cause a crash otherwise 1373 // ### TODO: now the emitEvent delays emission, this can be cleaned up 1374 delayRemove = true; 1375 1376 if (rescan_all) { 1377 // mark all as dirty 1378 it = m_mapEntries.begin(); 1379 for (; it != m_mapEntries.end(); ++it) { 1380 (*it).dirty = true; 1381 } 1382 rescan_all = false; 1383 } else { 1384 // propagate dirty flag to dependent entries (e.g. file watches) 1385 it = m_mapEntries.begin(); 1386 for (; it != m_mapEntries.end(); ++it) { 1387 if (((*it).m_mode == INotifyMode || (*it).m_mode == QFSWatchMode) && (*it).dirty) { 1388 (*it).propagate_dirty(); 1389 } 1390 } 1391 } 1392 1393 #if HAVE_SYS_INOTIFY_H 1394 QList<Entry *> cList; 1395 #endif 1396 1397 it = m_mapEntries.begin(); 1398 for (; it != m_mapEntries.end(); ++it) { 1399 // we don't check invalid entries (i.e. remove delayed) 1400 Entry *entry = &(*it); 1401 if (!entry->isValid()) { 1402 continue; 1403 } 1404 1405 const int ev = scanEntry(entry); 1406 if (s_verboseDebug) { 1407 qCDebug(KDIRWATCH) << "scanEntry for" << entry->path << "says" << ev; 1408 } 1409 1410 switch (entry->m_mode) { 1411 #if HAVE_SYS_INOTIFY_H 1412 case INotifyMode: 1413 if (ev == Deleted) { 1414 if (s_verboseDebug) { 1415 qCDebug(KDIRWATCH) << "scanEntry says" << entry->path << "was deleted"; 1416 } 1417 addEntry(nullptr, entry->parentDirectory(), entry, true); 1418 } else if (ev == Created) { 1419 if (s_verboseDebug) { 1420 qCDebug(KDIRWATCH) << "scanEntry says" << entry->path << "was created. wd=" << entry->wd; 1421 } 1422 if (entry->wd < 0) { 1423 cList.append(entry); 1424 addWatch(entry); 1425 } 1426 } 1427 break; 1428 #endif 1429 case QFSWatchMode: 1430 if (ev == Created) { 1431 addWatch(entry); 1432 } 1433 break; 1434 default: 1435 // dunno about StatMode... 1436 break; 1437 } 1438 1439 #if HAVE_SYS_INOTIFY_H 1440 if (entry->isDir) { 1441 // Report and clear the list of files that have changed in this directory. 1442 // Remove duplicates by changing to set and back again: 1443 // we don't really care about preserving the order of the 1444 // original changes. 1445 QStringList pendingFileChanges = entry->m_pendingFileChanges; 1446 pendingFileChanges.removeDuplicates(); 1447 for (const QString &changedFilename : std::as_const(pendingFileChanges)) { 1448 if (s_verboseDebug) { 1449 qCDebug(KDIRWATCH) << "processing pending file change for" << changedFilename; 1450 } 1451 emitEvent(entry, Changed, changedFilename); 1452 } 1453 entry->m_pendingFileChanges.clear(); 1454 } 1455 #endif 1456 1457 if (ev != NoChange) { 1458 emitEvent(entry, ev); 1459 } 1460 } 1461 1462 if (timerRunning) { 1463 m_statRescanTimer.start(freq); 1464 } 1465 1466 #if HAVE_SYS_INOTIFY_H 1467 // Remove watch of parent of new created directories 1468 for (Entry *e : std::as_const(cList)) { 1469 removeEntry(nullptr, e->parentDirectory(), e); 1470 } 1471 #endif 1472 1473 QTimer::singleShot(0, this, &KDirWatchPrivate::slotRemoveDelayed); 1474 } 1475 1476 bool KDirWatchPrivate::isNoisyFile(const char *filename) 1477 { 1478 // $HOME/.X.err grows with debug output, so don't notify change 1479 if (*filename == '.') { 1480 if (strncmp(filename, ".X.err", 6) == 0) { 1481 return true; 1482 } 1483 if (strncmp(filename, ".xsession-errors", 16) == 0) { 1484 return true; 1485 } 1486 // fontconfig updates the cache on every KDE app start 1487 // as well as during kio_thumbnail worker execution 1488 // TODO:; check which fontconfig version this file was deprecated and the check can be removed 1489 if (strncmp(filename, ".fonts.cache", 12) == 0) { 1490 return true; 1491 } 1492 } 1493 1494 return false; 1495 } 1496 1497 void KDirWatchPrivate::ref(KDirWatch *watch) 1498 { 1499 m_referencesObjects.push_back(watch); 1500 } 1501 1502 void KDirWatchPrivate::unref(KDirWatch *watch) 1503 { 1504 m_referencesObjects.removeOne(watch); 1505 if (m_referencesObjects.isEmpty()) { 1506 destroyPrivate(); 1507 } 1508 } 1509 1510 #if HAVE_SYS_INOTIFY_H 1511 QString KDirWatchPrivate::inotifyEventName(const inotify_event *event) const 1512 { 1513 if (event->mask & IN_OPEN) 1514 return QStringLiteral("OPEN"); 1515 else if (event->mask & IN_CLOSE_NOWRITE) 1516 return QStringLiteral("CLOSE_NOWRITE"); 1517 else if (event->mask & IN_CLOSE_WRITE) 1518 return QStringLiteral("CLOSE_WRITE"); 1519 else if (event->mask & IN_MOVED_TO) 1520 return QStringLiteral("MOVED_TO"); 1521 else if (event->mask & IN_MOVED_FROM) 1522 return QStringLiteral("MOVED_FROM"); 1523 else if (event->mask & IN_MOVE) 1524 return QStringLiteral("MOVE"); 1525 else if (event->mask & IN_CREATE) 1526 return QStringLiteral("CREATE"); 1527 else if (event->mask & IN_DELETE) 1528 return QStringLiteral("DELETE"); 1529 else if (event->mask & IN_DELETE_SELF) 1530 return QStringLiteral("DELETE_SELF"); 1531 else if (event->mask & IN_MOVE_SELF) 1532 return QStringLiteral("MOVE_SELF"); 1533 else if (event->mask & IN_ATTRIB) 1534 return QStringLiteral("ATTRIB"); 1535 else if (event->mask & IN_MODIFY) 1536 return QStringLiteral("MODIFY"); 1537 if (event->mask & IN_ACCESS) 1538 return QStringLiteral("ACCESS"); 1539 if (event->mask & IN_IGNORED) 1540 return QStringLiteral("IGNORED"); 1541 if (event->mask & IN_UNMOUNT) 1542 return QStringLiteral("IN_UNMOUNT"); 1543 else 1544 return QStringLiteral("UNKWOWN"); 1545 } 1546 #endif 1547 1548 #if HAVE_QFILESYSTEMWATCHER 1549 // Slot for QFileSystemWatcher 1550 void KDirWatchPrivate::fswEventReceived(const QString &path) 1551 { 1552 if (s_verboseDebug) { 1553 qCDebug(KDIRWATCH) << path; 1554 } 1555 1556 auto it = m_mapEntries.find(path); 1557 if (it != m_mapEntries.end()) { 1558 Entry *entry = &it.value(); 1559 entry->dirty = true; 1560 const int ev = scanEntry(entry); 1561 if (s_verboseDebug) { 1562 qCDebug(KDIRWATCH) << "scanEntry for" << entry->path << "says" << ev; 1563 } 1564 if (ev != NoChange) { 1565 emitEvent(entry, ev); 1566 } 1567 if (ev == Deleted) { 1568 if (entry->isDir) { 1569 addEntry(nullptr, entry->parentDirectory(), entry, true); 1570 } else { 1571 addEntry(nullptr, QFileInfo(entry->path).absolutePath(), entry, true); 1572 } 1573 } else if (ev == Created) { 1574 // We were waiting for it to appear; now watch it 1575 addWatch(entry); 1576 } else if (entry->isDir) { 1577 // Check if any file or dir was created under this directory, that we were waiting for 1578 for (Entry *sub_entry : std::as_const(entry->m_entries)) { 1579 fswEventReceived(sub_entry->path); // recurse, to call scanEntry and see if something changed 1580 } 1581 } else { 1582 /* Even though QFileSystemWatcher only reported the file as modified, it is possible that the file 1583 * was in fact just deleted and then immediately recreated. If the file was deleted, QFileSystemWatcher 1584 * will delete the watch, and will ignore the file, even after it is recreated. Since it is impossible 1585 * to reliably detect this case, always re-request the watch on a dirty signal, to avoid losing the 1586 * underlying OS monitor. 1587 */ 1588 fsWatcher->addPath(entry->path); 1589 } 1590 } 1591 } 1592 #else 1593 void KDirWatchPrivate::fswEventReceived(const QString &path) 1594 { 1595 Q_UNUSED(path); 1596 qCWarning(KCOREADDONS_DEBUG) << "QFileSystemWatcher event received but QFileSystemWatcher is not supported"; 1597 } 1598 #endif // HAVE_QFILESYSTEMWATCHER 1599 1600 // 1601 // Class KDirWatch 1602 // 1603 1604 Q_GLOBAL_STATIC(KDirWatch, s_pKDirWatchSelf) 1605 KDirWatch *KDirWatch::self() 1606 { 1607 return s_pKDirWatchSelf(); 1608 } 1609 1610 // <steve> is this used anywhere? 1611 // <dfaure> yes, see kio/src/core/kcoredirlister_p.h:328 1612 bool KDirWatch::exists() 1613 { 1614 return s_pKDirWatchSelf.exists() && dwp_self.hasLocalData(); 1615 } 1616 1617 KDirWatch::KDirWatch(QObject *parent) 1618 : QObject(parent) 1619 , d(createPrivate()) 1620 { 1621 d->ref(this); 1622 static QBasicAtomicInt nameCounter = Q_BASIC_ATOMIC_INITIALIZER(1); 1623 const int counter = nameCounter.fetchAndAddRelaxed(1); // returns the old value 1624 setObjectName(QStringLiteral("KDirWatch-%1").arg(counter)); 1625 } 1626 1627 KDirWatch::~KDirWatch() 1628 { 1629 if (d) { 1630 d->removeEntries(this); 1631 d->unref(this); 1632 } 1633 } 1634 1635 void KDirWatch::addDir(const QString &_path, WatchModes watchModes) 1636 { 1637 if (KNetworkMounts::self()->isOptionEnabledForPath(_path, KNetworkMounts::KDirWatchDontAddWatches)) { 1638 return; 1639 } 1640 1641 if (d) { 1642 d->addEntry(this, _path, nullptr, true, watchModes); 1643 } 1644 } 1645 1646 void KDirWatch::addFile(const QString &_path) 1647 { 1648 if (KNetworkMounts::self()->isOptionEnabledForPath(_path, KNetworkMounts::KDirWatchDontAddWatches)) { 1649 return; 1650 } 1651 1652 if (!d) { 1653 return; 1654 } 1655 1656 d->addEntry(this, _path, nullptr, false); 1657 } 1658 1659 QDateTime KDirWatch::ctime(const QString &_path) const 1660 { 1661 KDirWatchPrivate::Entry *e = d->entry(_path); 1662 1663 if (!e) { 1664 return QDateTime(); 1665 } 1666 1667 return QDateTime::fromSecsSinceEpoch(e->m_ctime); 1668 } 1669 1670 void KDirWatch::removeDir(const QString &_path) 1671 { 1672 if (d) { 1673 d->removeEntry(this, _path, nullptr); 1674 } 1675 } 1676 1677 void KDirWatch::removeFile(const QString &_path) 1678 { 1679 if (d) { 1680 d->removeEntry(this, _path, nullptr); 1681 } 1682 } 1683 1684 bool KDirWatch::stopDirScan(const QString &_path) 1685 { 1686 if (d) { 1687 KDirWatchPrivate::Entry *e = d->entry(_path); 1688 if (e && e->isDir) { 1689 return d->stopEntryScan(this, e); 1690 } 1691 } 1692 return false; 1693 } 1694 1695 bool KDirWatch::restartDirScan(const QString &_path) 1696 { 1697 if (d) { 1698 KDirWatchPrivate::Entry *e = d->entry(_path); 1699 if (e && e->isDir) 1700 // restart without notifying pending events 1701 { 1702 return d->restartEntryScan(this, e, false); 1703 } 1704 } 1705 return false; 1706 } 1707 1708 void KDirWatch::stopScan() 1709 { 1710 if (d) { 1711 d->stopScan(this); 1712 d->_isStopped = true; 1713 } 1714 } 1715 1716 bool KDirWatch::isStopped() 1717 { 1718 return d->_isStopped; 1719 } 1720 1721 void KDirWatch::startScan(bool notify, bool skippedToo) 1722 { 1723 if (d) { 1724 d->_isStopped = false; 1725 d->startScan(this, notify, skippedToo); 1726 } 1727 } 1728 1729 bool KDirWatch::contains(const QString &_path) const 1730 { 1731 KDirWatchPrivate::Entry *e = d->entry(_path); 1732 if (!e) { 1733 return false; 1734 } 1735 1736 for (const KDirWatchPrivate::Client &client : e->m_clients) { 1737 if (client.instance == this) { 1738 return true; 1739 } 1740 } 1741 1742 return false; 1743 } 1744 1745 void KDirWatch::setCreated(const QString &_file) 1746 { 1747 qCDebug(KDIRWATCH) << objectName() << "emitting created" << _file; 1748 Q_EMIT created(_file); 1749 } 1750 1751 void KDirWatch::setDirty(const QString &_file) 1752 { 1753 Q_EMIT dirty(_file); 1754 } 1755 1756 void KDirWatch::setDeleted(const QString &_file) 1757 { 1758 qCDebug(KDIRWATCH) << objectName() << "emitting deleted" << _file; 1759 Q_EMIT deleted(_file); 1760 } 1761 1762 KDirWatch::Method KDirWatch::internalMethod() const 1763 { 1764 // This reproduces the logic in KDirWatchPrivate::addWatch 1765 switch (d->m_preferredMethod) { 1766 case KDirWatch::INotify: 1767 #if HAVE_SYS_INOTIFY_H 1768 if (d->supports_inotify) { 1769 return KDirWatch::INotify; 1770 } 1771 #endif 1772 break; 1773 case KDirWatch::QFSWatch: 1774 #if HAVE_QFILESYSTEMWATCHER 1775 return KDirWatch::QFSWatch; 1776 #else 1777 break; 1778 #endif 1779 case KDirWatch::Stat: 1780 return KDirWatch::Stat; 1781 } 1782 1783 #if HAVE_SYS_INOTIFY_H 1784 if (d->supports_inotify) { 1785 return KDirWatch::INotify; 1786 } 1787 #endif 1788 #if HAVE_QFILESYSTEMWATCHER 1789 return KDirWatch::QFSWatch; 1790 #else 1791 return KDirWatch::Stat; 1792 #endif 1793 } 1794 1795 bool KDirWatch::event(QEvent *event) 1796 { 1797 if (Q_LIKELY(event->type() != QEvent::ThreadChange)) { 1798 return QObject::event(event); 1799 } 1800 1801 qCCritical(KDIRWATCH) << "KDirwatch is moving its thread. This is not supported at this time; your watch will not watch anything anymore!" 1802 << "Create and use watches on the correct thread" 1803 << "Watch:" << this; 1804 1805 // We are still in the old thread when the event runs, so this is safe. 1806 Q_ASSERT(thread() == d->thread()); 1807 d->removeEntries(this); 1808 d->unref(this); 1809 d = nullptr; 1810 1811 // Schedule the creation of the new private in the new thread. 1812 QMetaObject::invokeMethod( 1813 this, 1814 [this] { 1815 d = createPrivate(); 1816 }, 1817 Qt::QueuedConnection); 1818 1819 // NOTE: to actually support moving watches across threads we'd have to make Entry copyable and schedule a complete 1820 // re-installation of watches on the new thread after createPrivate. 1821 1822 return QObject::event(event); 1823 } 1824 1825 #include "moc_kdirwatch.cpp" 1826 #include "moc_kdirwatch_p.cpp" 1827 1828 // sven