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