1 /* This file is part of the KDE libraries
2    SPDX-FileCopyrightText: 1998 Sven Radej <sven@lisa.exp.univie.ac.at>
3    SPDX-FileCopyrightText: 2006 Dirk Mueller <mueller@kde.org>
4    SPDX-FileCopyrightText: 2007 Flavio Castelli <flavio.castelli@gmail.com>
5    SPDX-FileCopyrightText: 2008 Rafal Rzepecki <divided.mind@gmail.com>
6    SPDX-FileCopyrightText: 2010 David Faure <faure@kde.org>
7    SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
8 
9    SPDX-License-Identifier: LGPL-2.0-only
10 */
11 
12 // CHANGES:
13 // Jul 30, 2008 - Don't follow symlinks when recursing to avoid loops (Rafal)
14 // Aug 6,  2007 - KDirWatch::WatchModes support complete, flags work fine also
15 // when using FAMD (Flavio Castelli)
16 // Aug 3,  2007 - Handled KDirWatch::WatchModes flags when using inotify, now
17 // recursive and file monitoring modes are implemented (Flavio Castelli)
18 // Jul 30, 2007 - Substituted addEntry boolean params with KDirWatch::WatchModes
19 // flag (Flavio Castelli)
20 // Oct 4,  2005 - Inotify support (Dirk Mueller)
21 // February 2002 - Add file watching and remote mount check for STAT
22 // Mar 30, 2001 - Native support for Linux dir change notification.
23 // Jan 28, 2000 - Usage of FAM service on IRIX (Josef.Weidendorfer@in.tum.de)
24 // May 24. 1998 - List of times introduced, and some bugs are fixed. (sven)
25 // May 23. 1998 - Removed static pointer - you can have more instances.
26 // It was Needed for KRegistry. KDirWatch now emits signals and doesn't
27 // call (or need) KFM. No more URL's - just plain paths. (sven)
28 // Mar 29. 1998 - added docs, stop/restart for particular Dirs and
29 // deep copies for list of dirs. (sven)
30 // Mar 28. 1998 - Created.  (sven)
31 
32 #include "kdirwatch.h"
33 #include "kcoreaddons_debug.h"
34 #include "kdirwatch_p.h"
35 #include "kfilesystemtype.h"
36 #include "knetworkmounts.h"
37 
38 #include <io/config-kdirwatch.h>
39 
40 #include <QCoreApplication>
41 #include <QDir>
42 #include <QFile>
43 #include <QLoggingCategory>
44 #include <QSocketNotifier>
45 #include <QThread>
46 #include <QThreadStorage>
47 #include <QTimer>
48 #include <assert.h>
49 #include <cerrno>
50 #include <sys/stat.h>
51 
52 #include <qplatformdefs.h> // QT_LSTAT, QT_STAT, QT_STATBUF
53 
54 #include <stdlib.h>
55 #include <string.h>
56 
57 #if HAVE_SYS_INOTIFY_H
58 #include <fcntl.h>
59 #include <sys/inotify.h>
60 #include <unistd.h>
61 
62 #ifndef IN_DONT_FOLLOW
63 #define IN_DONT_FOLLOW 0x02000000
64 #endif
65 
66 #ifndef IN_ONLYDIR
67 #define IN_ONLYDIR 0x01000000
68 #endif
69 
70 // debug
71 #include <sys/ioctl.h>
72 
73 #include <sys/utsname.h>
74 
75 #endif // HAVE_SYS_INOTIFY_H
76 
77 Q_DECLARE_LOGGING_CATEGORY(KDIRWATCH)
78 // logging category for this framework, default: log stuff >= warning
79 Q_LOGGING_CATEGORY(KDIRWATCH, "kf.coreaddons.kdirwatch", QtWarningMsg)
80 
81 // set this to true for much more verbose debug output
82 static bool s_verboseDebug = false;
83 
84 static QThreadStorage<KDirWatchPrivate *> dwp_self;
createPrivate()85 static KDirWatchPrivate *createPrivate()
86 {
87     if (!dwp_self.hasLocalData()) {
88         dwp_self.setLocalData(new KDirWatchPrivate);
89     }
90     return dwp_self.localData();
91 }
destroyPrivate()92 static void destroyPrivate()
93 {
94     dwp_self.localData()->deleteLater();
95     dwp_self.setLocalData(nullptr);
96 }
97 
98 // Convert a string into a watch Method
methodFromString(const QByteArray & method)99 static KDirWatch::Method methodFromString(const QByteArray &method)
100 {
101     if (method == "Fam") {
102         return KDirWatch::FAM;
103     } else if (method == "Stat") {
104         return KDirWatch::Stat;
105     } else if (method == "QFSWatch") {
106         return KDirWatch::QFSWatch;
107     } else {
108 #if HAVE_SYS_INOTIFY_H
109         // inotify supports delete+recreate+modify, which QFSWatch doesn't support
110         return KDirWatch::INotify;
111 #else
112         return KDirWatch::QFSWatch;
113 #endif
114     }
115 }
116 
methodToString(KDirWatch::Method method)117 static const char *methodToString(KDirWatch::Method method)
118 {
119     switch (method) {
120     case KDirWatch::FAM:
121         return "Fam";
122     case KDirWatch::INotify:
123         return "INotify";
124     case KDirWatch::Stat:
125         return "Stat";
126     case KDirWatch::QFSWatch:
127         return "QFSWatch";
128     }
129     // not reached
130     return nullptr;
131 }
132 
133 static const char s_envNfsPoll[] = "KDIRWATCH_NFSPOLLINTERVAL";
134 static const char s_envPoll[] = "KDIRWATCH_POLLINTERVAL";
135 static const char s_envMethod[] = "KDIRWATCH_METHOD";
136 static const char s_envNfsMethod[] = "KDIRWATCH_NFSMETHOD";
137 
138 //
139 // Class KDirWatchPrivate (singleton)
140 //
141 
142 /* All entries (files/directories) to be watched in the
143  * application (coming from multiple KDirWatch instances)
144  * are registered in a single KDirWatchPrivate instance.
145  *
146  * At the moment, the following methods for file watching
147  * are supported:
148  * - Polling: All files to be watched are polled regularly
149  *   using stat (more precise: QFileInfo.lastModified()).
150  *   The polling frequency is determined from global kconfig
151  *   settings, defaulting to 500 ms for local directories
152  *   and 5000 ms for remote mounts
153  * - FAM (File Alternation Monitor): first used on IRIX, SGI
154  *   has ported this method to LINUX. It uses a kernel part
155  *   (IMON, sending change events to /dev/imon) and a user
156  *   level daemon (fam), to which applications connect for
157  *   notification of file changes. For NFS, the fam daemon
158  *   on the NFS server machine is used; if IMON is not built
159  *   into the kernel, fam uses polling for local files.
160  * - INOTIFY: In LINUX 2.6.13, inode change notification was
161  *   introduced. You're now able to watch arbitrary inode's
162  *   for changes, and even get notification when they're
163  *   unmounted.
164  */
165 
KDirWatchPrivate()166 KDirWatchPrivate::KDirWatchPrivate()
167     : timer()
168     , freq(3600000)
169     , // 1 hour as upper bound
170     statEntries(0)
171     , delayRemove(false)
172     , rescan_all(false)
173     , rescan_timer()
174     ,
175 #if HAVE_SYS_INOTIFY_H
176     mSn(nullptr)
177     ,
178 #endif
179     _isStopped(false)
180     , m_references(0)
181 {
182     // Debug unittest on CI
183     if (qAppName() == QLatin1String("kservicetest") || qAppName() == QLatin1String("filetypestest")) {
184         s_verboseDebug = true;
185     }
186     timer.setObjectName(QStringLiteral("KDirWatchPrivate::timer"));
187     connect(&timer, &QTimer::timeout, this, &KDirWatchPrivate::slotRescan);
188 
189     m_nfsPollInterval = qEnvironmentVariableIsSet(s_envNfsPoll) ? qEnvironmentVariableIntValue(s_envNfsPoll) : 5000;
190     m_PollInterval = qEnvironmentVariableIsSet(s_envPoll) ? qEnvironmentVariableIntValue(s_envPoll) : 500;
191 
192     m_preferredMethod = methodFromString(qEnvironmentVariableIsSet(s_envMethod) ? qgetenv(s_envMethod) : "inotify");
193     // The nfs method defaults to the normal (local) method
194     m_nfsPreferredMethod = methodFromString(qEnvironmentVariableIsSet(s_envNfsMethod) ? qgetenv(s_envNfsMethod) : "Fam");
195 
196     QList<QByteArray> availableMethods;
197 
198     availableMethods << "Stat";
199 
200     // used for FAM and inotify
201     rescan_timer.setObjectName(QStringLiteral("KDirWatchPrivate::rescan_timer"));
202     rescan_timer.setSingleShot(true);
203     connect(&rescan_timer, &QTimer::timeout, this, &KDirWatchPrivate::slotRescan);
204 
205 #if HAVE_FAM
206     availableMethods << "FAM";
207     use_fam = true;
208     sn = nullptr;
209 #endif
210 
211 #if HAVE_SYS_INOTIFY_H
212     supports_inotify = true;
213 
214     m_inotify_fd = inotify_init();
215 
216     if (m_inotify_fd <= 0) {
217         qCDebug(KDIRWATCH) << "Can't use Inotify, kernel doesn't support it:" << strerror(errno);
218         supports_inotify = false;
219     }
220 
221     // qCDebug(KDIRWATCH) << "INotify available: " << supports_inotify;
222     if (supports_inotify) {
223         availableMethods << "INotify";
224         (void)fcntl(m_inotify_fd, F_SETFD, FD_CLOEXEC);
225 
226         mSn = new QSocketNotifier(m_inotify_fd, QSocketNotifier::Read, this);
227         connect(mSn, SIGNAL(activated(int)), this, SLOT(inotifyEventReceived()));
228     }
229 #endif
230 #if HAVE_QFILESYSTEMWATCHER
231     availableMethods << "QFileSystemWatcher";
232     fsWatcher = nullptr;
233 #endif
234 
235     if (s_verboseDebug) {
236         qCDebug(KDIRWATCH) << "Available methods: " << availableMethods << "preferred=" << methodToString(m_preferredMethod);
237     }
238 }
239 
240 // This is called on app exit (deleted by QThreadStorage)
~KDirWatchPrivate()241 KDirWatchPrivate::~KDirWatchPrivate()
242 {
243     timer.stop();
244 
245 #if HAVE_FAM
246     if (use_fam && sn) {
247         FAMClose(&fc);
248     }
249 #endif
250 #if HAVE_SYS_INOTIFY_H
251     if (supports_inotify) {
252         QT_CLOSE(m_inotify_fd);
253     }
254 #endif
255 #if HAVE_QFILESYSTEMWATCHER
256     delete fsWatcher;
257 #endif
258 }
259 
inotifyEventReceived()260 void KDirWatchPrivate::inotifyEventReceived()
261 {
262 #if HAVE_SYS_INOTIFY_H
263     if (!supports_inotify) {
264         return;
265     }
266 
267     int pending = -1;
268     int offsetStartRead = 0; // where we read into buffer
269     char buf[8192];
270     assert(m_inotify_fd > -1);
271     ioctl(m_inotify_fd, FIONREAD, &pending);
272 
273     while (pending > 0) {
274         const int bytesToRead = qMin<int>(pending, sizeof(buf) - offsetStartRead);
275 
276         int bytesAvailable = read(m_inotify_fd, &buf[offsetStartRead], bytesToRead);
277         pending -= bytesAvailable;
278         bytesAvailable += offsetStartRead;
279         offsetStartRead = 0;
280 
281         int offsetCurrent = 0;
282         while (bytesAvailable >= int(sizeof(struct inotify_event))) {
283             const struct inotify_event *const event = reinterpret_cast<inotify_event *>(&buf[offsetCurrent]);
284             const int eventSize = sizeof(struct inotify_event) + event->len;
285             if (bytesAvailable < eventSize) {
286                 break;
287             }
288 
289             bytesAvailable -= eventSize;
290             offsetCurrent += eventSize;
291 
292             QString path;
293             // strip trailing null chars, see inotify_event documentation
294             // these must not end up in the final QString version of path
295             int len = event->len;
296             while (len > 1 && !event->name[len - 1]) {
297                 --len;
298             }
299             QByteArray cpath(event->name, len);
300             if (len) {
301                 path = QFile::decodeName(cpath);
302             }
303 
304             if (!path.isEmpty() && isNoisyFile(cpath.data())) {
305                 continue;
306             }
307 
308             // Is set to true if the new event is a directory, false otherwise. This prevents a stat call in clientsForFileOrDir
309             const bool isDir = (event->mask & (IN_ISDIR));
310 
311             Entry *e = m_inotify_wd_to_entry.value(event->wd);
312             if (!e) {
313                 continue;
314             }
315             const bool wasDirty = e->dirty;
316             e->dirty = true;
317 
318             const QString tpath = e->path + QLatin1Char('/') + path;
319 
320             if (s_verboseDebug) {
321                 qCDebug(KDIRWATCH).nospace() << "got event 0x" << qPrintable(QString::number(event->mask, 16)) << " for " << e->path;
322             }
323 
324             if (event->mask & IN_DELETE_SELF) {
325                 if (s_verboseDebug) {
326                     qCDebug(KDIRWATCH) << "-->got deleteself signal for" << e->path;
327                 }
328                 e->m_status = NonExistent;
329                 m_inotify_wd_to_entry.remove(e->wd);
330                 e->wd = -1;
331                 e->m_ctime = invalid_ctime;
332                 emitEvent(e, Deleted, e->path);
333                 // If the parent dir was already watched, tell it something changed
334                 Entry *parentEntry = entry(e->parentDirectory());
335                 if (parentEntry) {
336                     parentEntry->dirty = true;
337                 }
338                 // Add entry to parent dir to notice if the entry gets recreated
339                 addEntry(nullptr, e->parentDirectory(), e, true /*isDir*/);
340             }
341             if (event->mask & IN_IGNORED) {
342                 // Causes bug #207361 with kernels 2.6.31 and 2.6.32!
343                 // e->wd = -1;
344             }
345             if (event->mask & (IN_CREATE | IN_MOVED_TO)) {
346                 Entry *sub_entry = e->findSubEntry(tpath);
347 
348                 if (s_verboseDebug) {
349                     qCDebug(KDIRWATCH) << "-->got CREATE signal for" << (tpath) << "sub_entry=" << sub_entry;
350                     qCDebug(KDIRWATCH) << *e;
351                 }
352 
353                 // The code below is very similar to the one in checkFAMEvent...
354                 if (sub_entry) {
355                     // We were waiting for this new file/dir to be created
356                     sub_entry->dirty = true;
357                     rescan_timer.start(0); // process this asap, to start watching that dir
358                 } else if (e->isDir && !e->m_clients.empty()) {
359                     const QList<const Client *> clients = e->inotifyClientsForFileOrDir(isDir);
360                     // See discussion in addEntry for why we don't addEntry for individual
361                     // files in WatchFiles mode with inotify.
362                     if (isDir) {
363                         for (const Client *client : clients) {
364                             addEntry(client->instance, tpath, nullptr, isDir, isDir ? client->m_watchModes : KDirWatch::WatchDirOnly);
365                         }
366                     }
367                     if (!clients.isEmpty()) {
368                         emitEvent(e, Created, tpath);
369                         qCDebug(KDIRWATCH).nospace() << clients.count() << " instance(s) monitoring the new " << (isDir ? "dir " : "file ") << tpath;
370                     }
371                     e->m_pendingFileChanges.append(e->path);
372                     if (!rescan_timer.isActive()) {
373                         rescan_timer.start(m_PollInterval); // singleshot
374                     }
375                 }
376             }
377             if (event->mask & (IN_DELETE | IN_MOVED_FROM)) {
378                 if (s_verboseDebug) {
379                     qCDebug(KDIRWATCH) << "-->got DELETE signal for" << tpath;
380                 }
381                 if ((e->isDir) && (!e->m_clients.empty())) {
382                     // A file in this directory has been removed.  It wasn't an explicitly
383                     // watched file as it would have its own watch descriptor, so
384                     // no addEntry/ removeEntry bookkeeping should be required.  Emit
385                     // the event immediately if any clients are interested.
386                     KDirWatch::WatchModes flag = isDir ? KDirWatch::WatchSubDirs : KDirWatch::WatchFiles;
387                     int counter = 0;
388                     for (const Client &client : e->m_clients) {
389                         if (client.m_watchModes & flag) {
390                             counter++;
391                         }
392                     }
393                     if (counter != 0) {
394                         emitEvent(e, Deleted, tpath);
395                     }
396                 }
397             }
398             if (event->mask & (IN_MODIFY | IN_ATTRIB)) {
399                 if ((e->isDir) && (!e->m_clients.empty())) {
400                     if (s_verboseDebug) {
401                         qCDebug(KDIRWATCH) << "-->got MODIFY signal for" << (tpath);
402                     }
403                     // A file in this directory has been changed.  No
404                     // addEntry/ removeEntry bookkeeping should be required.
405                     // Add the path to the list of pending file changes if
406                     // there are any interested clients.
407                     // QT_STATBUF stat_buf;
408                     // QByteArray tpath = QFile::encodeName(e->path+'/'+path);
409                     // QT_STAT(tpath, &stat_buf);
410                     // bool isDir = S_ISDIR(stat_buf.st_mode);
411 
412                     // The API doc is somewhat vague as to whether we should emit
413                     // dirty() for implicitly watched files when WatchFiles has
414                     // not been specified - we'll assume they are always interested,
415                     // regardless.
416                     // Don't worry about duplicates for the time
417                     // being; this is handled in slotRescan.
418                     e->m_pendingFileChanges.append(tpath);
419                     // Avoid stat'ing the directory if only an entry inside it changed.
420                     e->dirty = (wasDirty || (path.isEmpty() && (event->mask & IN_ATTRIB)));
421                 }
422             }
423 
424             if (!rescan_timer.isActive()) {
425                 rescan_timer.start(m_PollInterval); // singleshot
426             }
427         }
428         if (bytesAvailable > 0) {
429             // copy partial event to beginning of buffer
430             memmove(buf, &buf[offsetCurrent], bytesAvailable);
431             offsetStartRead = bytesAvailable;
432         }
433     }
434 #endif
435 }
436 
~Entry()437 KDirWatchPrivate::Entry::~Entry()
438 {
439 }
440 
441 /* In FAM mode, only entries which are marked dirty are scanned.
442  * We first need to mark all yet nonexistent, but possible created
443  * entries as dirty...
444  */
propagate_dirty()445 void KDirWatchPrivate::Entry::propagate_dirty()
446 {
447     for (Entry *sub_entry : std::as_const(m_entries)) {
448         if (!sub_entry->dirty) {
449             sub_entry->dirty = true;
450             sub_entry->propagate_dirty();
451         }
452     }
453 }
454 
455 /* A KDirWatch instance is interested in getting events for
456  * this file/Dir entry.
457  */
addClient(KDirWatch * instance,KDirWatch::WatchModes watchModes)458 void KDirWatchPrivate::Entry::addClient(KDirWatch *instance, KDirWatch::WatchModes watchModes)
459 {
460     if (instance == nullptr) {
461         return;
462     }
463 
464     for (Client &client : m_clients) {
465         if (client.instance == instance) {
466             client.count++;
467             client.m_watchModes = watchModes;
468             return;
469         }
470     }
471 
472     m_clients.emplace_back(instance, watchModes);
473 }
474 
removeClient(KDirWatch * instance)475 void KDirWatchPrivate::Entry::removeClient(KDirWatch *instance)
476 {
477     auto it = m_clients.begin();
478     const auto end = m_clients.end();
479     for (; it != end; ++it) {
480         Client &client = *it;
481         if (client.instance == instance) {
482             client.count--;
483             if (client.count == 0) {
484                 m_clients.erase(it);
485             }
486             return;
487         }
488     }
489 }
490 
491 /* get number of clients */
clientCount() const492 int KDirWatchPrivate::Entry::clientCount() const
493 {
494     int clients = 0;
495     for (const Client &client : m_clients) {
496         clients += client.count;
497     }
498 
499     return clients;
500 }
501 
parentDirectory() const502 QString KDirWatchPrivate::Entry::parentDirectory() const
503 {
504     return QDir::cleanPath(path + QLatin1String("/.."));
505 }
506 
clientsForFileOrDir(const QString & tpath,bool * isDir) const507 QList<const KDirWatchPrivate::Client *> KDirWatchPrivate::Entry::clientsForFileOrDir(const QString &tpath, bool *isDir) const
508 {
509     QList<const Client *> ret;
510     QFileInfo fi(tpath);
511     if (fi.exists()) {
512         *isDir = fi.isDir();
513         const KDirWatch::WatchModes flag = *isDir ? KDirWatch::WatchSubDirs : KDirWatch::WatchFiles;
514         for (const Client &client : m_clients) {
515             if (client.m_watchModes & flag) {
516                 ret.append(&client);
517             }
518         }
519     } else {
520         // Happens frequently, e.g. ERROR: couldn't stat "/home/dfaure/.viminfo.tmp"
521         // qCDebug(KDIRWATCH) << "ERROR: couldn't stat" << tpath;
522         // In this case isDir is not set, but ret is empty anyway
523         // so isDir won't be used.
524     }
525     return ret;
526 }
527 
528 // inotify specific function that doesn't call KDE::stat to figure out if we have a file or folder.
529 // isDir is determined through inotify's "IN_ISDIR" flag in KDirWatchPrivate::inotifyEventReceived
inotifyClientsForFileOrDir(bool isDir) const530 QList<const KDirWatchPrivate::Client *> KDirWatchPrivate::Entry::inotifyClientsForFileOrDir(bool isDir) const
531 {
532     QList<const Client *> ret;
533     const KDirWatch::WatchModes flag = isDir ? KDirWatch::WatchSubDirs : KDirWatch::WatchFiles;
534     for (const Client &client : m_clients) {
535         if (client.m_watchModes & flag) {
536             ret.append(&client);
537         }
538     }
539     return ret;
540 }
541 
operator <<(QDebug debug,const KDirWatchPrivate::Entry & entry)542 QDebug operator<<(QDebug debug, const KDirWatchPrivate::Entry &entry)
543 {
544     debug.nospace() << "[ Entry for " << entry.path << ", " << (entry.isDir ? "dir" : "file");
545     if (entry.m_status == KDirWatchPrivate::NonExistent) {
546         debug << ", non-existent";
547     }
548     debug << ", using "
549           << ((entry.m_mode == KDirWatchPrivate::FAMMode)            ? "FAM"
550                   : (entry.m_mode == KDirWatchPrivate::INotifyMode)  ? "INotify"
551                   : (entry.m_mode == KDirWatchPrivate::QFSWatchMode) ? "QFSWatch"
552                   : (entry.m_mode == KDirWatchPrivate::StatMode)     ? "Stat"
553                                                                      : "Unknown Method");
554 #if HAVE_SYS_INOTIFY_H
555     if (entry.m_mode == KDirWatchPrivate::INotifyMode) {
556         debug << " inotify_wd=" << entry.wd;
557     }
558 #endif
559     debug << ", has " << entry.m_clients.size() << " clients";
560     debug.space();
561     if (!entry.m_entries.isEmpty()) {
562         debug << ", nonexistent subentries:";
563         for (KDirWatchPrivate::Entry *subEntry : std::as_const(entry.m_entries)) {
564             debug << subEntry << subEntry->path;
565         }
566     }
567     debug << ']';
568     return debug;
569 }
570 
entry(const QString & _path)571 KDirWatchPrivate::Entry *KDirWatchPrivate::entry(const QString &_path)
572 {
573     // we only support absolute paths
574     if (_path.isEmpty() || QDir::isRelativePath(_path)) {
575         return nullptr;
576     }
577 
578     QString path(_path);
579 
580     if (path.length() > 1 && path.endsWith(QLatin1Char('/'))) {
581         path.chop(1);
582     }
583 
584     EntryMap::Iterator it = m_mapEntries.find(path);
585     if (it == m_mapEntries.end()) {
586         return nullptr;
587     } else {
588         return &(*it);
589     }
590 }
591 
592 // set polling frequency for a entry and adjust global freq if needed
useFreq(Entry * e,int newFreq)593 void KDirWatchPrivate::useFreq(Entry *e, int newFreq)
594 {
595     e->freq = newFreq;
596 
597     // a reasonable frequency for the global polling timer
598     if (e->freq < freq) {
599         freq = e->freq;
600         if (timer.isActive()) {
601             timer.start(freq);
602         }
603         qCDebug(KDIRWATCH) << "Global Poll Freq is now" << freq << "msec";
604     }
605 }
606 
607 #if HAVE_FAM
608 // setup FAM notification, returns false if not possible
useFAM(Entry * e)609 bool KDirWatchPrivate::useFAM(Entry *e)
610 {
611     if (!use_fam) {
612         return false;
613     }
614 
615     if (!sn) {
616         if (FAMOpen(&fc) == 0) {
617             sn = new QSocketNotifier(FAMCONNECTION_GETFD(&fc), QSocketNotifier::Read, this);
618             connect(sn, SIGNAL(activated(int)), this, SLOT(famEventReceived()));
619         } else {
620             use_fam = false;
621             return false;
622         }
623     }
624 
625     // handle FAM events to avoid deadlock
626     // (FAM sends back all files in a directory when monitoring)
627     famEventReceived();
628 
629     e->m_mode = FAMMode;
630     e->dirty = false;
631     e->m_famReportedSeen = false;
632 
633     bool startedFAMMonitor = false;
634 
635     if (e->isDir) {
636         if (e->m_status == NonExistent) {
637             // If the directory does not exist we watch the parent directory
638             addEntry(nullptr, e->parentDirectory(), e, true);
639         } else {
640             int res = FAMMonitorDirectory(&fc, QFile::encodeName(e->path).data(), &(e->fr), e);
641             startedFAMMonitor = true;
642             if (res < 0) {
643                 e->m_mode = UnknownMode;
644                 use_fam = false;
645                 delete sn;
646                 sn = nullptr;
647                 return false;
648             }
649             qCDebug(KDIRWATCH).nospace() << " Setup FAM (Req " << FAMREQUEST_GETREQNUM(&(e->fr)) << ") for " << e->path;
650         }
651     } else {
652         if (e->m_status == NonExistent) {
653             // If the file does not exist we watch the directory
654             addEntry(nullptr, QFileInfo(e->path).absolutePath(), e, true);
655         } else {
656             int res = FAMMonitorFile(&fc, QFile::encodeName(e->path).data(), &(e->fr), e);
657             startedFAMMonitor = true;
658             if (res < 0) {
659                 e->m_mode = UnknownMode;
660                 use_fam = false;
661                 delete sn;
662                 sn = nullptr;
663                 return false;
664             }
665 
666             qCDebug(KDIRWATCH).nospace() << " Setup FAM (Req " << FAMREQUEST_GETREQNUM(&(e->fr)) << ") for " << e->path;
667         }
668     }
669 
670     // handle FAM events to avoid deadlock
671     // (FAM sends back all files in a directory when monitoring)
672     const int iterationCap = 80;
673     for (int i = 0; i <= iterationCap; ++i) { // we'll not wait forever; blocking for 4s seems plenty
674         famEventReceived();
675         // NB: check use_fam, if fam is defunct event receiving might disable fam support!
676         if (use_fam && startedFAMMonitor && !e->m_famReportedSeen) {
677             // 50 is ~half the time it takes to setup a watch.  If gamin's latency
678             // gets better, this can be reduced.
679             QThread::msleep(50);
680         } else if (use_fam && i == iterationCap) {
681             disableFAM();
682             return false;
683         } else {
684             break;
685         }
686     }
687 
688     return true;
689 }
690 #endif
691 
692 #if HAVE_SYS_INOTIFY_H
693 // setup INotify notification, returns false if not possible
useINotify(Entry * e)694 bool KDirWatchPrivate::useINotify(Entry *e)
695 {
696     e->wd = -1;
697     e->dirty = false;
698 
699     if (!supports_inotify) {
700         return false;
701     }
702 
703     e->m_mode = INotifyMode;
704 
705     if (e->m_status == NonExistent) {
706         addEntry(nullptr, e->parentDirectory(), e, true);
707         return true;
708     }
709 
710     // May as well register for almost everything - it's free!
711     int mask = IN_DELETE | IN_DELETE_SELF | IN_CREATE | IN_MOVE | IN_MOVE_SELF | IN_DONT_FOLLOW | IN_MOVED_FROM | IN_MODIFY | IN_ATTRIB;
712 
713     if ((e->wd = inotify_add_watch(m_inotify_fd, QFile::encodeName(e->path).data(), mask)) != -1) {
714         m_inotify_wd_to_entry.insert(e->wd, e);
715         if (s_verboseDebug) {
716             qCDebug(KDIRWATCH) << "inotify successfully used for monitoring" << e->path << "wd=" << e->wd;
717         }
718         return true;
719     }
720 
721     if (errno == ENOSPC) {
722         // Inotify max_user_watches was reached (/proc/sys/fs/inotify/max_user_watches)
723         // See man inotify_add_watch, https://github.com/guard/listen/wiki/Increasing-the-amount-of-inotify-watchers
724         qCWarning(KDIRWATCH) << "inotify failed for monitoring" << e->path << "\n"
725                              << "Because it reached its max_user_watches,\n"
726                              << "you can increase the maximum number of file watches per user,\n"
727                              << "by setting an appropriate fs.inotify.max_user_watches parameter in your /etc/sysctl.conf";
728     } else {
729         qCDebug(KDIRWATCH) << "inotify failed for monitoring" << e->path << ":" << strerror(errno) << " (errno:" << errno << ")";
730     }
731     return false;
732 }
733 #endif
734 #if HAVE_QFILESYSTEMWATCHER
useQFSWatch(Entry * e)735 bool KDirWatchPrivate::useQFSWatch(Entry *e)
736 {
737     e->m_mode = QFSWatchMode;
738     e->dirty = false;
739 
740     if (e->m_status == NonExistent) {
741         addEntry(nullptr, e->parentDirectory(), e, true /*isDir*/);
742         return true;
743     }
744 
745     // qCDebug(KDIRWATCH) << "fsWatcher->addPath" << e->path;
746     if (!fsWatcher) {
747         fsWatcher = new QFileSystemWatcher();
748         connect(fsWatcher, &QFileSystemWatcher::directoryChanged, this, &KDirWatchPrivate::fswEventReceived);
749         connect(fsWatcher, &QFileSystemWatcher::fileChanged, this, &KDirWatchPrivate::fswEventReceived);
750     }
751     fsWatcher->addPath(e->path);
752     return true;
753 }
754 #endif
755 
useStat(Entry * e)756 bool KDirWatchPrivate::useStat(Entry *e)
757 {
758     if (KFileSystemType::fileSystemType(e->path) == KFileSystemType::Nfs) { // TODO: or Smbfs?
759         useFreq(e, m_nfsPollInterval);
760     } else {
761         useFreq(e, m_PollInterval);
762     }
763 
764     if (e->m_mode != StatMode) {
765         e->m_mode = StatMode;
766         statEntries++;
767 
768         if (statEntries == 1) {
769             // if this was first STAT entry (=timer was stopped)
770             timer.start(freq); // then start the timer
771             qCDebug(KDIRWATCH) << " Started Polling Timer, freq " << freq;
772         }
773     }
774 
775     qCDebug(KDIRWATCH) << " Setup Stat (freq " << e->freq << ") for " << e->path;
776 
777     return true;
778 }
779 
780 /* If <instance> !=0, this KDirWatch instance wants to watch at <_path>,
781  * providing in <isDir> the type of the entry to be watched.
782  * Sometimes, entries are dependent on each other: if <sub_entry> !=0,
783  * this entry needs another entry to watch itself (when notExistent).
784  */
addEntry(KDirWatch * instance,const QString & _path,Entry * sub_entry,bool isDir,KDirWatch::WatchModes watchModes)785 void KDirWatchPrivate::addEntry(KDirWatch *instance, const QString &_path, Entry *sub_entry, bool isDir, KDirWatch::WatchModes watchModes)
786 {
787     QString path(_path);
788     if (path.startsWith(QLatin1String(":/"))) {
789         qCWarning(KDIRWATCH) << "Cannot watch QRC-like path" << path;
790         return;
791     }
792     if (path.isEmpty()
793 #ifndef Q_OS_WIN
794         || path == QLatin1String("/dev")
795         || (path.startsWith(QLatin1String("/dev/")) && !path.startsWith(QLatin1String("/dev/.")) && !path.startsWith(QLatin1String("/dev/shm")))
796 #endif
797     ) {
798         return; // Don't even go there.
799     }
800 
801     if (path.length() > 1 && path.endsWith(QLatin1Char('/'))) {
802         path.chop(1);
803     }
804 
805     EntryMap::Iterator it = m_mapEntries.find(path);
806     if (it != m_mapEntries.end()) {
807         if (sub_entry) {
808             (*it).m_entries.append(sub_entry);
809             if (s_verboseDebug) {
810                 qCDebug(KDIRWATCH) << "Added already watched Entry" << path << "(for" << sub_entry->path << ")";
811             }
812         } else {
813             (*it).addClient(instance, watchModes);
814             if (s_verboseDebug) {
815                 qCDebug(KDIRWATCH) << "Added already watched Entry" << path << "(now" << (*it).clientCount() << "clients)"
816                                    << QStringLiteral("[%1]").arg(instance->objectName());
817             }
818         }
819         return;
820     }
821 
822     // we have a new path to watch
823 
824     QT_STATBUF stat_buf;
825     bool exists = (QT_STAT(QFile::encodeName(path).constData(), &stat_buf) == 0);
826 
827     EntryMap::iterator newIt = m_mapEntries.insert(path, Entry());
828     // the insert does a copy, so we have to use <e> now
829     Entry *e = &(*newIt);
830 
831     if (exists) {
832         e->isDir = (stat_buf.st_mode & QT_STAT_MASK) == QT_STAT_DIR;
833 
834 #ifndef Q_OS_WIN
835         if (e->isDir && !isDir) {
836             if (QT_LSTAT(QFile::encodeName(path).constData(), &stat_buf) == 0) {
837                 if ((stat_buf.st_mode & QT_STAT_MASK) == QT_STAT_LNK) {
838                     // if it's a symlink, don't follow it
839                     e->isDir = false;
840                 }
841             }
842         }
843 #endif
844 
845         if (e->isDir && !isDir) {
846             qCWarning(KCOREADDONS_DEBUG) << "KDirWatch:" << path << "is a directory. Use addDir!";
847         } else if (!e->isDir && isDir) {
848             qCWarning(KCOREADDONS_DEBUG) << "KDirWatch:" << path << "is a file. Use addFile!";
849         }
850 
851         if (!e->isDir && (watchModes != KDirWatch::WatchDirOnly)) {
852             qCWarning(KCOREADDONS_DEBUG) << "KDirWatch:" << path
853                                          << "is a file. You can't use recursive or "
854                                             "watchFiles options";
855             watchModes = KDirWatch::WatchDirOnly;
856         }
857 
858 #ifdef Q_OS_WIN
859         // ctime is the 'creation time' on windows - use mtime instead
860         e->m_ctime = stat_buf.st_mtime;
861 #else
862         e->m_ctime = stat_buf.st_ctime;
863 #endif
864         e->m_status = Normal;
865         e->m_nlink = stat_buf.st_nlink;
866         e->m_ino = stat_buf.st_ino;
867     } else {
868         e->isDir = isDir;
869         e->m_ctime = invalid_ctime;
870         e->m_status = NonExistent;
871         e->m_nlink = 0;
872         e->m_ino = 0;
873     }
874 
875     e->path = path;
876     if (sub_entry) {
877         e->m_entries.append(sub_entry);
878     } else {
879         e->addClient(instance, watchModes);
880     }
881 
882     if (s_verboseDebug) {
883         qCDebug(KDIRWATCH).nospace() << "Added " << (e->isDir ? "Dir " : "File ") << path << (e->m_status == NonExistent ? " NotExisting" : "") << " for "
884                                      << (sub_entry ? sub_entry->path : QString()) << " [" << (instance ? instance->objectName() : QString()) << "]";
885     }
886 
887     // now setup the notification method
888     e->m_mode = UnknownMode;
889     e->msecLeft = 0;
890 
891     if (isNoisyFile(QFile::encodeName(path).data())) {
892         return;
893     }
894 
895     if (exists && e->isDir && (watchModes != KDirWatch::WatchDirOnly)) {
896         QFlags<QDir::Filter> filters = QDir::NoDotAndDotDot;
897 
898         if ((watchModes & KDirWatch::WatchSubDirs) && (watchModes & KDirWatch::WatchFiles)) {
899             filters |= (QDir::Dirs | QDir::Files);
900         } else if (watchModes & KDirWatch::WatchSubDirs) {
901             filters |= QDir::Dirs;
902         } else if (watchModes & KDirWatch::WatchFiles) {
903             filters |= QDir::Files;
904         }
905 
906 #if HAVE_SYS_INOTIFY_H
907         if (e->m_mode == INotifyMode || (e->m_mode == UnknownMode && m_preferredMethod == KDirWatch::INotify)) {
908             // qCDebug(KDIRWATCH) << "Ignoring WatchFiles directive - this is implicit with inotify";
909             // Placing a watch on individual files is redundant with inotify
910             // (inotify gives us WatchFiles functionality "for free") and indeed
911             // actively harmful, so prevent it.  WatchSubDirs is necessary, though.
912             filters &= ~QDir::Files;
913         }
914 #endif
915 
916         QDir basedir(e->path);
917         const QFileInfoList contents = basedir.entryInfoList(filters);
918         for (QFileInfoList::const_iterator iter = contents.constBegin(); iter != contents.constEnd(); ++iter) {
919             const QFileInfo &fileInfo = *iter;
920             // treat symlinks as files--don't follow them.
921             bool isDir = fileInfo.isDir() && !fileInfo.isSymLink();
922 
923             addEntry(instance, fileInfo.absoluteFilePath(), nullptr, isDir, isDir ? watchModes : KDirWatch::WatchDirOnly);
924         }
925     }
926 
927     addWatch(e);
928 }
929 
addWatch(Entry * e)930 void KDirWatchPrivate::addWatch(Entry *e)
931 {
932     // If the watch is on a network filesystem use the nfsPreferredMethod as the
933     // default, otherwise use preferredMethod as the default, if the methods are
934     // the same we can skip the mountpoint check
935 
936     // This allows to configure a different method for NFS mounts, since inotify
937     // cannot detect changes made by other machines. However as a default inotify
938     // is fine, since the most common case is a NFS-mounted home, where all changes
939     // are made locally. #177892.
940 
941     KDirWatch::Method preferredMethod = m_preferredMethod;
942     if (KNetworkMounts::self()->isOptionEnabledForPath(e->path, KNetworkMounts::KDirWatchUseINotify)) {
943         preferredMethod = KDirWatch::INotify;
944     } else if (m_nfsPreferredMethod != m_preferredMethod) {
945         if (KFileSystemType::fileSystemType(e->path) == KFileSystemType::Nfs) {
946             preferredMethod = m_nfsPreferredMethod;
947         }
948     }
949 
950     // Try the appropriate preferred method from the config first
951     bool entryAdded = false;
952     switch (preferredMethod) {
953 #if HAVE_FAM
954     case KDirWatch::FAM:
955         entryAdded = useFAM(e);
956         break;
957 #else
958     case KDirWatch::FAM:
959         entryAdded = false;
960         break;
961 #endif
962 #if HAVE_SYS_INOTIFY_H
963     case KDirWatch::INotify:
964         entryAdded = useINotify(e);
965         break;
966 #else
967     case KDirWatch::INotify:
968         entryAdded = false;
969         break;
970 #endif
971 #if HAVE_QFILESYSTEMWATCHER
972     case KDirWatch::QFSWatch:
973         entryAdded = useQFSWatch(e);
974         break;
975 #else
976     case KDirWatch::QFSWatch:
977         entryAdded = false;
978         break;
979 #endif
980     case KDirWatch::Stat:
981         entryAdded = useStat(e);
982         break;
983     }
984 
985     // Failing that try in order INotify, FAM, QFSWatch, Stat
986     if (!entryAdded) {
987 #if HAVE_SYS_INOTIFY_H
988         if (useINotify(e)) {
989             return;
990         }
991 #endif
992 #if HAVE_FAM
993         if (useFAM(e)) {
994             return;
995         }
996 #endif
997 #if HAVE_QFILESYSTEMWATCHER
998         if (useQFSWatch(e)) {
999             return;
1000         }
1001 #endif
1002         useStat(e);
1003     }
1004 }
1005 
removeWatch(Entry * e)1006 void KDirWatchPrivate::removeWatch(Entry *e)
1007 {
1008 #if HAVE_FAM
1009     if (e->m_mode == FAMMode) {
1010         FAMCancelMonitor(&fc, &(e->fr));
1011         qCDebug(KDIRWATCH).nospace() << "Cancelled FAM (Req " << FAMREQUEST_GETREQNUM(&(e->fr)) << ") for " << e->path;
1012     }
1013 #endif
1014 #if HAVE_SYS_INOTIFY_H
1015     if (e->m_mode == INotifyMode) {
1016         m_inotify_wd_to_entry.remove(e->wd);
1017         (void)inotify_rm_watch(m_inotify_fd, e->wd);
1018         if (s_verboseDebug) {
1019             qCDebug(KDIRWATCH).nospace() << "Cancelled INotify (fd " << m_inotify_fd << ", " << e->wd << ") for " << e->path;
1020         }
1021     }
1022 #endif
1023 #if HAVE_QFILESYSTEMWATCHER
1024     if (e->m_mode == QFSWatchMode && fsWatcher) {
1025         if (s_verboseDebug) {
1026             qCDebug(KDIRWATCH) << "fsWatcher->removePath" << e->path;
1027         }
1028         fsWatcher->removePath(e->path);
1029     }
1030 #endif
1031 }
1032 
removeEntry(KDirWatch * instance,const QString & _path,Entry * sub_entry)1033 void KDirWatchPrivate::removeEntry(KDirWatch *instance, const QString &_path, Entry *sub_entry)
1034 {
1035     if (s_verboseDebug) {
1036         qCDebug(KDIRWATCH) << "path=" << _path << "sub_entry:" << sub_entry;
1037     }
1038     Entry *e = entry(_path);
1039     if (e) {
1040         removeEntry(instance, e, sub_entry);
1041     }
1042 }
1043 
removeEntry(KDirWatch * instance,Entry * e,Entry * sub_entry)1044 void KDirWatchPrivate::removeEntry(KDirWatch *instance, Entry *e, Entry *sub_entry)
1045 {
1046     removeList.remove(e);
1047 
1048     if (sub_entry) {
1049         e->m_entries.removeAll(sub_entry);
1050     } else {
1051         e->removeClient(instance);
1052     }
1053 
1054     if (!e->m_clients.empty() || !e->m_entries.empty()) {
1055         return;
1056     }
1057 
1058     if (delayRemove) {
1059         removeList.insert(e);
1060         // now e->isValid() is false
1061         return;
1062     }
1063 
1064     if (e->m_status == Normal) {
1065         removeWatch(e);
1066     } else {
1067         // Removed a NonExistent entry - we just remove it from the parent
1068         if (e->isDir) {
1069             removeEntry(nullptr, e->parentDirectory(), e);
1070         } else {
1071             removeEntry(nullptr, QFileInfo(e->path).absolutePath(), e);
1072         }
1073     }
1074 
1075     if (e->m_mode == StatMode) {
1076         statEntries--;
1077         if (statEntries == 0) {
1078             timer.stop(); // stop timer if lists are empty
1079             qCDebug(KDIRWATCH) << " Stopped Polling Timer";
1080         }
1081     }
1082 
1083     if (s_verboseDebug) {
1084         qCDebug(KDIRWATCH).nospace() << "Removed " << (e->isDir ? "Dir " : "File ") << e->path << " for " << (sub_entry ? sub_entry->path : QString()) << " ["
1085                                      << (instance ? instance->objectName() : QString()) << "]";
1086     }
1087     QString p = e->path; // take a copy, QMap::remove takes a reference and deletes, since e points into the map
1088 #if HAVE_SYS_INOTIFY_H
1089     m_inotify_wd_to_entry.remove(e->wd);
1090 #endif
1091     m_mapEntries.remove(p); // <e> not valid any more
1092 }
1093 
1094 /* Called from KDirWatch destructor:
1095  * remove <instance> as client from all entries
1096  */
removeEntries(KDirWatch * instance)1097 void KDirWatchPrivate::removeEntries(KDirWatch *instance)
1098 {
1099     int minfreq = 3600000;
1100 
1101     QStringList pathList;
1102     // put all entries where instance is a client in list
1103     EntryMap::Iterator it = m_mapEntries.begin();
1104     for (; it != m_mapEntries.end(); ++it) {
1105         Client *c = nullptr;
1106         for (Client &client : (*it).m_clients) {
1107             if (client.instance == instance) {
1108                 c = &client;
1109                 break;
1110             }
1111         }
1112         if (c) {
1113             c->count = 1; // forces deletion of instance as client
1114             pathList.append((*it).path);
1115         } else if ((*it).m_mode == StatMode && (*it).freq < minfreq) {
1116             minfreq = (*it).freq;
1117         }
1118     }
1119 
1120     for (const QString &path : std::as_const(pathList)) {
1121         removeEntry(instance, path, nullptr);
1122     }
1123 
1124     if (minfreq > freq) {
1125         // we can decrease the global polling frequency
1126         freq = minfreq;
1127         if (timer.isActive()) {
1128             timer.start(freq);
1129         }
1130         qCDebug(KDIRWATCH) << "Poll Freq now" << freq << "msec";
1131     }
1132 }
1133 
1134 // instance ==0: stop scanning for all instances
stopEntryScan(KDirWatch * instance,Entry * e)1135 bool KDirWatchPrivate::stopEntryScan(KDirWatch *instance, Entry *e)
1136 {
1137     int stillWatching = 0;
1138     for (Client &client : e->m_clients) {
1139         if (!instance || instance == client.instance) {
1140             client.watchingStopped = true;
1141         } else if (!client.watchingStopped) {
1142             stillWatching += client.count;
1143         }
1144     }
1145 
1146     qCDebug(KDIRWATCH) << (instance ? instance->objectName() : QStringLiteral("all")) << "stopped scanning" << e->path << "(now" << stillWatching
1147                        << "watchers)";
1148 
1149     if (stillWatching == 0) {
1150         // if nobody is interested, we don't watch, and we don't report
1151         // changes that happened while not watching
1152         e->m_ctime = invalid_ctime; // invalid
1153 
1154         // Changing m_status like this would create wrong "created" events in stat mode.
1155         // To really "stop watching" we would need to determine 'stillWatching==0' in scanEntry...
1156         // e->m_status = NonExistent;
1157     }
1158     return true;
1159 }
1160 
1161 // instance ==0: start scanning for all instances
restartEntryScan(KDirWatch * instance,Entry * e,bool notify)1162 bool KDirWatchPrivate::restartEntryScan(KDirWatch *instance, Entry *e, bool notify)
1163 {
1164     int wasWatching = 0;
1165     int newWatching = 0;
1166     for (Client &client : e->m_clients) {
1167         if (!client.watchingStopped) {
1168             wasWatching += client.count;
1169         } else if (!instance || instance == client.instance) {
1170             client.watchingStopped = false;
1171             newWatching += client.count;
1172         }
1173     }
1174     if (newWatching == 0) {
1175         return false;
1176     }
1177 
1178     qCDebug(KDIRWATCH) << (instance ? instance->objectName() : QStringLiteral("all")) << "restarted scanning" << e->path << "(now" << wasWatching + newWatching
1179                        << "watchers)";
1180 
1181     // restart watching and emit pending events
1182 
1183     int ev = NoChange;
1184     if (wasWatching == 0) {
1185         if (!notify) {
1186             QT_STATBUF stat_buf;
1187             bool exists = (QT_STAT(QFile::encodeName(e->path).constData(), &stat_buf) == 0);
1188             if (exists) {
1189                 // ctime is the 'creation time' on windows, but with qMax
1190                 // we get the latest change of any kind, on any platform.
1191                 e->m_ctime = qMax(stat_buf.st_ctime, stat_buf.st_mtime);
1192                 e->m_status = Normal;
1193                 if (s_verboseDebug) {
1194                     qCDebug(KDIRWATCH) << "Setting status to Normal for" << e << e->path;
1195                 }
1196                 e->m_nlink = stat_buf.st_nlink;
1197                 e->m_ino = stat_buf.st_ino;
1198 
1199                 // Same as in scanEntry: ensure no subentry in parent dir
1200                 removeEntry(nullptr, e->parentDirectory(), e);
1201             } else {
1202                 e->m_ctime = invalid_ctime;
1203                 e->m_status = NonExistent;
1204                 e->m_nlink = 0;
1205                 if (s_verboseDebug) {
1206                     qCDebug(KDIRWATCH) << "Setting status to NonExistent for" << e << e->path;
1207                 }
1208             }
1209         }
1210         e->msecLeft = 0;
1211         ev = scanEntry(e);
1212     }
1213     emitEvent(e, ev);
1214 
1215     return true;
1216 }
1217 
1218 // instance ==0: stop scanning for all instances
stopScan(KDirWatch * instance)1219 void KDirWatchPrivate::stopScan(KDirWatch *instance)
1220 {
1221     EntryMap::Iterator it = m_mapEntries.begin();
1222     for (; it != m_mapEntries.end(); ++it) {
1223         stopEntryScan(instance, &(*it));
1224     }
1225 }
1226 
startScan(KDirWatch * instance,bool notify,bool skippedToo)1227 void KDirWatchPrivate::startScan(KDirWatch *instance, bool notify, bool skippedToo)
1228 {
1229     if (!notify) {
1230         resetList(instance, skippedToo);
1231     }
1232 
1233     EntryMap::Iterator it = m_mapEntries.begin();
1234     for (; it != m_mapEntries.end(); ++it) {
1235         restartEntryScan(instance, &(*it), notify);
1236     }
1237 
1238     // timer should still be running when in polling mode
1239 }
1240 
1241 // clear all pending events, also from stopped
resetList(KDirWatch * instance,bool skippedToo)1242 void KDirWatchPrivate::resetList(KDirWatch *instance, bool skippedToo)
1243 {
1244     Q_UNUSED(instance);
1245     EntryMap::Iterator it = m_mapEntries.begin();
1246     for (; it != m_mapEntries.end(); ++it) {
1247         for (Client &client : (*it).m_clients) {
1248             if (!client.watchingStopped || skippedToo) {
1249                 client.pending = NoChange;
1250             }
1251         }
1252     }
1253 }
1254 
1255 // Return event happened on <e>
1256 //
scanEntry(Entry * e)1257 int KDirWatchPrivate::scanEntry(Entry *e)
1258 {
1259     // Shouldn't happen: Ignore "unknown" notification method
1260     if (e->m_mode == UnknownMode) {
1261         return NoChange;
1262     }
1263 
1264     if (e->m_mode == FAMMode || e->m_mode == INotifyMode) {
1265         // we know nothing has changed, no need to stat
1266         if (!e->dirty) {
1267             return NoChange;
1268         }
1269         e->dirty = false;
1270     }
1271 
1272     if (e->m_mode == StatMode) {
1273         // only scan if timeout on entry timer happens;
1274         // e.g. when using 500msec global timer, a entry
1275         // with freq=5000 is only watched every 10th time
1276 
1277         e->msecLeft -= freq;
1278         if (e->msecLeft > 0) {
1279             return NoChange;
1280         }
1281         e->msecLeft += e->freq;
1282     }
1283 
1284     QT_STATBUF stat_buf;
1285     const bool exists = (QT_STAT(QFile::encodeName(e->path).constData(), &stat_buf) == 0);
1286     if (exists) {
1287         if (e->m_status == NonExistent) {
1288             // ctime is the 'creation time' on windows, but with qMax
1289             // we get the latest change of any kind, on any platform.
1290             e->m_ctime = qMax(stat_buf.st_ctime, stat_buf.st_mtime);
1291             e->m_status = Normal;
1292             e->m_ino = stat_buf.st_ino;
1293             if (s_verboseDebug) {
1294                 qCDebug(KDIRWATCH) << "Setting status to Normal for just created" << e << e->path;
1295             }
1296             // We need to make sure the entry isn't listed in its parent's subentries... (#222974, testMoveTo)
1297             removeEntry(nullptr, e->parentDirectory(), e);
1298 
1299             return Created;
1300         }
1301 
1302 #if 1 // for debugging the if() below
1303         if (s_verboseDebug) {
1304             struct tm *tmp = localtime(&e->m_ctime);
1305             char outstr[200];
1306             strftime(outstr, sizeof(outstr), "%H:%M:%S", tmp);
1307             qCDebug(KDIRWATCH) << e->path << "e->m_ctime=" << e->m_ctime << outstr << "stat_buf.st_ctime=" << stat_buf.st_ctime
1308                                << "stat_buf.st_mtime=" << stat_buf.st_mtime << "e->m_nlink=" << e->m_nlink << "stat_buf.st_nlink=" << stat_buf.st_nlink
1309                                << "e->m_ino=" << e->m_ino << "stat_buf.st_ino=" << stat_buf.st_ino;
1310         }
1311 #endif
1312 
1313         if ((e->m_ctime != invalid_ctime)
1314             && (qMax(stat_buf.st_ctime, stat_buf.st_mtime) != e->m_ctime || stat_buf.st_ino != e->m_ino
1315                 || int(stat_buf.st_nlink) != int(e->m_nlink)
1316 #ifdef Q_OS_WIN
1317                 // on Windows, we trust QFSW to get it right, the ctime comparisons above
1318                 // fail for example when adding files to directories on Windows
1319                 // which doesn't change the mtime of the directory
1320                 || e->m_mode == QFSWatchMode
1321 #endif
1322                 )) {
1323             e->m_ctime = qMax(stat_buf.st_ctime, stat_buf.st_mtime);
1324             e->m_nlink = stat_buf.st_nlink;
1325             if (e->m_ino != stat_buf.st_ino) {
1326                 // The file got deleted and recreated. We need to watch it again.
1327                 removeWatch(e);
1328                 addWatch(e);
1329                 e->m_ino = stat_buf.st_ino;
1330                 return (Deleted | Created);
1331             } else {
1332                 return Changed;
1333             }
1334         }
1335 
1336         return NoChange;
1337     }
1338 
1339     // dir/file doesn't exist
1340 
1341     e->m_nlink = 0;
1342     e->m_ino = 0;
1343     e->m_status = NonExistent;
1344 
1345     if (e->m_ctime == invalid_ctime) {
1346         return NoChange;
1347     }
1348 
1349     e->m_ctime = invalid_ctime;
1350     return Deleted;
1351 }
1352 
1353 /* Notify all interested KDirWatch instances about a given event on an entry
1354  * and stored pending events. When watching is stopped, the event is
1355  * added to the pending events.
1356  */
emitEvent(Entry * e,int event,const QString & fileName)1357 void KDirWatchPrivate::emitEvent(Entry *e, int event, const QString &fileName)
1358 {
1359     QString path(e->path);
1360     if (!fileName.isEmpty()) {
1361         if (!QDir::isRelativePath(fileName)) {
1362             path = fileName;
1363         } else {
1364 #ifdef Q_OS_UNIX
1365             path += QLatin1Char('/') + fileName;
1366 #elif defined(Q_OS_WIN)
1367             // current drive is passed instead of /
1368             path += QStringView(QDir::currentPath()).left(2) + QLatin1Char('/') + fileName;
1369 #endif
1370         }
1371     }
1372 
1373     if (s_verboseDebug) {
1374         qCDebug(KDIRWATCH) << event << path << e->m_clients.size() << "clients";
1375     }
1376 
1377     for (Client &c : e->m_clients) {
1378         if (c.instance == nullptr || c.count == 0) {
1379             continue;
1380         }
1381 
1382         if (c.watchingStopped) {
1383             // Do not add event to a list of pending events, the docs say restartDirScan won't emit!
1384             continue;
1385         }
1386         // not stopped
1387         if (event == NoChange || event == Changed) {
1388             event |= c.pending;
1389         }
1390         c.pending = NoChange;
1391         if (event == NoChange) {
1392             continue;
1393         }
1394 
1395         // Emit the signals delayed, to avoid unexpected re-entrance from the slots (#220153)
1396 
1397         if (event & Deleted) {
1398             QMetaObject::invokeMethod(
1399                 c.instance,
1400                 [c, path]() {
1401                     c.instance->setDeleted(path);
1402                 },
1403                 Qt::QueuedConnection);
1404         }
1405 
1406         if (event & Created) {
1407             QMetaObject::invokeMethod(
1408                 c.instance,
1409                 [c, path]() {
1410                     c.instance->setCreated(path);
1411                 },
1412                 Qt::QueuedConnection);
1413             // possible emit Change event after creation
1414         }
1415 
1416         if (event & Changed) {
1417             QMetaObject::invokeMethod(
1418                 c.instance,
1419                 [c, path]() {
1420                     c.instance->setDirty(path);
1421                 },
1422                 Qt::QueuedConnection);
1423         }
1424     }
1425 }
1426 
1427 // Remove entries which were marked to be removed
slotRemoveDelayed()1428 void KDirWatchPrivate::slotRemoveDelayed()
1429 {
1430     delayRemove = false;
1431     // Removing an entry could also take care of removing its parent
1432     // (e.g. in FAM or inotify mode), which would remove other entries in removeList,
1433     // so don't use Q_FOREACH or iterators here...
1434     while (!removeList.isEmpty()) {
1435         Entry *entry = *removeList.begin();
1436         removeEntry(nullptr, entry, nullptr); // this will remove entry from removeList
1437     }
1438 }
1439 
1440 /* Scan all entries to be watched for changes. This is done regularly
1441  * when polling. FAM and inotify use a single-shot timer to call this slot delayed.
1442  */
slotRescan()1443 void KDirWatchPrivate::slotRescan()
1444 {
1445     if (s_verboseDebug) {
1446         qCDebug(KDIRWATCH);
1447     }
1448 
1449     EntryMap::Iterator it;
1450 
1451     // People can do very long things in the slot connected to dirty(),
1452     // like showing a message box. We don't want to keep polling during
1453     // that time, otherwise the value of 'delayRemove' will be reset.
1454     // ### TODO: now the emitEvent delays emission, this can be cleaned up
1455     bool timerRunning = timer.isActive();
1456     if (timerRunning) {
1457         timer.stop();
1458     }
1459 
1460     // We delay deletions of entries this way.
1461     // removeDir(), when called in slotDirty(), can cause a crash otherwise
1462     // ### TODO: now the emitEvent delays emission, this can be cleaned up
1463     delayRemove = true;
1464 
1465     if (rescan_all) {
1466         // mark all as dirty
1467         it = m_mapEntries.begin();
1468         for (; it != m_mapEntries.end(); ++it) {
1469             (*it).dirty = true;
1470         }
1471         rescan_all = false;
1472     } else {
1473         // propagate dirty flag to dependent entries (e.g. file watches)
1474         it = m_mapEntries.begin();
1475         for (; it != m_mapEntries.end(); ++it) {
1476             if (((*it).m_mode == INotifyMode || (*it).m_mode == QFSWatchMode) && (*it).dirty) {
1477                 (*it).propagate_dirty();
1478             }
1479         }
1480     }
1481 
1482 #if HAVE_SYS_INOTIFY_H
1483     QList<Entry *> cList;
1484 #endif
1485 
1486     it = m_mapEntries.begin();
1487     for (; it != m_mapEntries.end(); ++it) {
1488         // we don't check invalid entries (i.e. remove delayed)
1489         Entry *entry = &(*it);
1490         if (!entry->isValid()) {
1491             continue;
1492         }
1493 
1494         const int ev = scanEntry(entry);
1495         if (s_verboseDebug) {
1496             qCDebug(KDIRWATCH) << "scanEntry for" << entry->path << "says" << ev;
1497         }
1498 
1499         switch (entry->m_mode) {
1500 #if HAVE_SYS_INOTIFY_H
1501         case INotifyMode:
1502             if (ev == Deleted) {
1503                 if (s_verboseDebug) {
1504                     qCDebug(KDIRWATCH) << "scanEntry says" << entry->path << "was deleted";
1505                 }
1506                 addEntry(nullptr, entry->parentDirectory(), entry, true);
1507             } else if (ev == Created) {
1508                 if (s_verboseDebug) {
1509                     qCDebug(KDIRWATCH) << "scanEntry says" << entry->path << "was created. wd=" << entry->wd;
1510                 }
1511                 if (entry->wd < 0) {
1512                     cList.append(entry);
1513                     addWatch(entry);
1514                 }
1515             }
1516             break;
1517 #endif
1518         case FAMMode:
1519         case QFSWatchMode:
1520             if (ev == Created) {
1521                 addWatch(entry);
1522             }
1523             break;
1524         default:
1525             // dunno about StatMode...
1526             break;
1527         }
1528 
1529 #if HAVE_SYS_INOTIFY_H
1530         if (entry->isDir) {
1531             // Report and clear the list of files that have changed in this directory.
1532             // Remove duplicates by changing to set and back again:
1533             // we don't really care about preserving the order of the
1534             // original changes.
1535             QStringList pendingFileChanges = entry->m_pendingFileChanges;
1536             pendingFileChanges.removeDuplicates();
1537             for (const QString &changedFilename : std::as_const(pendingFileChanges)) {
1538                 if (s_verboseDebug) {
1539                     qCDebug(KDIRWATCH) << "processing pending file change for" << changedFilename;
1540                 }
1541                 emitEvent(entry, Changed, changedFilename);
1542             }
1543             entry->m_pendingFileChanges.clear();
1544         }
1545 #endif
1546 
1547         if (ev != NoChange) {
1548             emitEvent(entry, ev);
1549         }
1550     }
1551 
1552     if (timerRunning) {
1553         timer.start(freq);
1554     }
1555 
1556 #if HAVE_SYS_INOTIFY_H
1557     // Remove watch of parent of new created directories
1558     for (Entry *e : std::as_const(cList)) {
1559         removeEntry(nullptr, e->parentDirectory(), e);
1560     }
1561 #endif
1562 
1563     QTimer::singleShot(0, this, &KDirWatchPrivate::slotRemoveDelayed);
1564 }
1565 
isNoisyFile(const char * filename)1566 bool KDirWatchPrivate::isNoisyFile(const char *filename)
1567 {
1568     // $HOME/.X.err grows with debug output, so don't notify change
1569     if (*filename == '.') {
1570         if (strncmp(filename, ".X.err", 6) == 0) {
1571             return true;
1572         }
1573         if (strncmp(filename, ".xsession-errors", 16) == 0) {
1574             return true;
1575         }
1576         // fontconfig updates the cache on every KDE app start
1577         // (inclusive kio_thumbnail slaves)
1578         if (strncmp(filename, ".fonts.cache", 12) == 0) {
1579             return true;
1580         }
1581     }
1582 
1583     return false;
1584 }
1585 
ref()1586 void KDirWatchPrivate::ref()
1587 {
1588     ++m_references;
1589 }
1590 
unref()1591 void KDirWatchPrivate::unref()
1592 {
1593     --m_references;
1594     if (m_references == 0) {
1595         destroyPrivate();
1596     }
1597 }
1598 
1599 #if HAVE_FAM
famEventReceived()1600 void KDirWatchPrivate::famEventReceived()
1601 {
1602     static FAMEvent fe;
1603 
1604     delayRemove = true;
1605 
1606     while (use_fam && FAMPending(&fc)) {
1607         if (FAMNextEvent(&fc, &fe) == -1) {
1608             disableFAM();
1609         } else {
1610             checkFAMEvent(&fe);
1611         }
1612     }
1613 
1614     QTimer::singleShot(0, this, &KDirWatchPrivate::slotRemoveDelayed);
1615 }
1616 
disableFAM()1617 void KDirWatchPrivate::disableFAM()
1618 {
1619     qCWarning(KCOREADDONS_DEBUG) << "FAM connection problem, switching to a different system.";
1620     use_fam = false;
1621     delete sn;
1622     sn = nullptr;
1623 
1624     // Replace all FAMMode entries with another system (INotify/QFSW/Stat)
1625     for (auto it = m_mapEntries.begin(); it != m_mapEntries.end(); ++it) {
1626         if ((*it).m_mode == FAMMode && !(*it).m_clients.empty()) {
1627             Entry *e = &(*it);
1628             addWatch(e);
1629         }
1630     }
1631 }
1632 
checkFAMEvent(FAMEvent * fe)1633 void KDirWatchPrivate::checkFAMEvent(FAMEvent *fe)
1634 {
1635     Entry *e = nullptr;
1636     EntryMap::Iterator it = m_mapEntries.begin();
1637     for (; it != m_mapEntries.end(); ++it)
1638         if (FAMREQUEST_GETREQNUM(&((*it).fr)) == FAMREQUEST_GETREQNUM(&(fe->fr))) {
1639             e = &(*it);
1640             break;
1641         }
1642 
1643     // Don't be too verbose ;-)
1644     if ((fe->code == FAMExists) || (fe->code == FAMEndExist) || (fe->code == FAMAcknowledge)) {
1645         if (e) {
1646             e->m_famReportedSeen = true;
1647         }
1648         return;
1649     }
1650 
1651     if (isNoisyFile(fe->filename)) {
1652         return;
1653     }
1654 
1655     // Entry *e = static_cast<Entry*>(fe->userdata);
1656 
1657     if (s_verboseDebug) { // don't enable this except when debugging, see #88538
1658         qCDebug(KDIRWATCH) << "Processing FAM event ("
1659                            << ((fe->code == FAMChanged)              ? "FAMChanged"
1660                                    : (fe->code == FAMDeleted)        ? "FAMDeleted"
1661                                    : (fe->code == FAMStartExecuting) ? "FAMStartExecuting"
1662                                    : (fe->code == FAMStopExecuting)  ? "FAMStopExecuting"
1663                                    : (fe->code == FAMCreated)        ? "FAMCreated"
1664                                    : (fe->code == FAMMoved)          ? "FAMMoved"
1665                                    : (fe->code == FAMAcknowledge)    ? "FAMAcknowledge"
1666                                    : (fe->code == FAMExists)         ? "FAMExists"
1667                                    : (fe->code == FAMEndExist)       ? "FAMEndExist"
1668                                                                      : "Unknown Code")
1669                            << ", " << fe->filename << ", Req " << FAMREQUEST_GETREQNUM(&(fe->fr)) << ") e=" << e;
1670     }
1671 
1672     if (!e) {
1673         // this happens e.g. for FAMAcknowledge after deleting a dir...
1674         //    qCDebug(KDIRWATCH) << "No entry for FAM event ?!";
1675         return;
1676     }
1677 
1678     if (e->m_status == NonExistent) {
1679         qCDebug(KDIRWATCH) << "FAM event for nonExistent entry " << e->path;
1680         return;
1681     }
1682 
1683     // Delayed handling. This rechecks changes with own stat calls.
1684     e->dirty = true;
1685     if (!rescan_timer.isActive()) {
1686         rescan_timer.start(m_PollInterval); // singleshot
1687     }
1688 
1689     // needed FAM control actions on FAM events
1690     switch (fe->code) {
1691     case FAMDeleted:
1692         // fe->filename is an absolute path when a watched file-or-dir is deleted
1693         if (!QDir::isRelativePath(QFile::decodeName(fe->filename))) {
1694             FAMCancelMonitor(&fc, &(e->fr)); // needed ?
1695             qCDebug(KDIRWATCH) << "Cancelled FAMReq" << FAMREQUEST_GETREQNUM(&(e->fr)) << "for" << e->path;
1696             e->m_status = NonExistent;
1697             e->m_ctime = invalid_ctime;
1698             emitEvent(e, Deleted, e->path);
1699             // If the parent dir was already watched, tell it something changed
1700             Entry *parentEntry = entry(e->parentDirectory());
1701             if (parentEntry) {
1702                 parentEntry->dirty = true;
1703             }
1704             // Add entry to parent dir to notice if the entry gets recreated
1705             addEntry(nullptr, e->parentDirectory(), e, true /*isDir*/);
1706         } else {
1707             // A file in this directory has been removed, and wasn't explicitly watched.
1708             // We could still inform clients, like inotify does? But stat can't.
1709             // For now we just marked e dirty and slotRescan will emit the dir as dirty.
1710             // qCDebug(KDIRWATCH) << "Got FAMDeleted for" << QFile::decodeName(fe->filename) << "in" << e->path << ". Absolute path -> NOOP!";
1711         }
1712         break;
1713 
1714     case FAMCreated: {
1715         // check for creation of a directory we have to watch
1716         QString tpath(e->path + QLatin1Char('/') + QFile::decodeName(fe->filename));
1717 
1718         // This code is very similar to the one in inotifyEventReceived...
1719         Entry *sub_entry = e->findSubEntry(tpath);
1720         if (sub_entry /*&& sub_entry->isDir*/) {
1721             // We were waiting for this new file/dir to be created.  We don't actually
1722             // emit an event here, as the rescan_timer will re-detect the creation and
1723             // do the signal emission there.
1724             sub_entry->dirty = true;
1725             rescan_timer.start(0); // process this asap, to start watching that dir
1726         } else if (e->isDir && !e->m_clients.empty()) {
1727             bool isDir = false;
1728             const QList<const Client *> clients = e->clientsForFileOrDir(tpath, &isDir);
1729             for (const Client *client : clients) {
1730                 addEntry(client->instance, tpath, nullptr, isDir, isDir ? client->m_watchModes : KDirWatch::WatchDirOnly);
1731             }
1732 
1733             if (!clients.isEmpty()) {
1734                 emitEvent(e, Created, tpath);
1735 
1736                 qCDebug(KDIRWATCH).nospace() << clients.count() << " instance(s) monitoring the new " << (isDir ? "dir " : "file ") << tpath;
1737             }
1738         }
1739     } break;
1740     default:
1741         break;
1742     }
1743 }
1744 #else
famEventReceived()1745 void KDirWatchPrivate::famEventReceived()
1746 {
1747     qCWarning(KCOREADDONS_DEBUG) << "Fam event received but FAM is not supported";
1748 }
1749 #endif
1750 
statistics()1751 void KDirWatchPrivate::statistics()
1752 {
1753     EntryMap::Iterator it;
1754 
1755     qCDebug(KDIRWATCH) << "Entries watched:";
1756     if (m_mapEntries.count() == 0) {
1757         qCDebug(KDIRWATCH) << "  None.";
1758     } else {
1759         it = m_mapEntries.begin();
1760         for (; it != m_mapEntries.end(); ++it) {
1761             Entry *e = &(*it);
1762             qCDebug(KDIRWATCH) << "  " << *e;
1763 
1764             for (const Client &c : e->m_clients) {
1765                 QByteArray pending;
1766                 if (c.watchingStopped) {
1767                     if (c.pending & Deleted) {
1768                         pending += "deleted ";
1769                     }
1770                     if (c.pending & Created) {
1771                         pending += "created ";
1772                     }
1773                     if (c.pending & Changed) {
1774                         pending += "changed ";
1775                     }
1776                     if (!pending.isEmpty()) {
1777                         pending = " (pending: " + pending + ')';
1778                     }
1779                     pending = ", stopped" + pending;
1780                 }
1781                 qCDebug(KDIRWATCH) << "    by " << c.instance->objectName() << " (" << c.count << " times)" << pending;
1782             }
1783             if (!e->m_entries.isEmpty()) {
1784                 qCDebug(KDIRWATCH) << "    dependent entries:";
1785                 for (Entry *d : std::as_const(e->m_entries)) {
1786                     qCDebug(KDIRWATCH) << "      " << d << d->path << (d->m_status == NonExistent ? "NonExistent" : "EXISTS!!! ERROR!");
1787                     if (s_verboseDebug) {
1788                         Q_ASSERT(d->m_status == NonExistent); // it doesn't belong here otherwise
1789                     }
1790                 }
1791             }
1792         }
1793     }
1794 }
1795 
1796 #if HAVE_QFILESYSTEMWATCHER
1797 // Slot for QFileSystemWatcher
fswEventReceived(const QString & path)1798 void KDirWatchPrivate::fswEventReceived(const QString &path)
1799 {
1800     if (s_verboseDebug) {
1801         qCDebug(KDIRWATCH) << path;
1802     }
1803     EntryMap::Iterator it = m_mapEntries.find(path);
1804     if (it != m_mapEntries.end()) {
1805         Entry *e = &(*it);
1806         e->dirty = true;
1807         const int ev = scanEntry(e);
1808         if (s_verboseDebug) {
1809             qCDebug(KDIRWATCH) << "scanEntry for" << e->path << "says" << ev;
1810         }
1811         if (ev != NoChange) {
1812             emitEvent(e, ev);
1813         }
1814         if (ev == Deleted) {
1815             if (e->isDir) {
1816                 addEntry(nullptr, e->parentDirectory(), e, true);
1817             } else {
1818                 addEntry(nullptr, QFileInfo(e->path).absolutePath(), e, true);
1819             }
1820         } else if (ev == Created) {
1821             // We were waiting for it to appear; now watch it
1822             addWatch(e);
1823         } else if (e->isDir) {
1824             // Check if any file or dir was created under this directory, that we were waiting for
1825             for (Entry *sub_entry : std::as_const(e->m_entries)) {
1826                 fswEventReceived(sub_entry->path); // recurse, to call scanEntry and see if something changed
1827             }
1828         } else {
1829             /* Even though QFileSystemWatcher only reported the file as modified, it is possible that the file
1830              * was in fact just deleted and then immediately recreated.  If the file was deleted, QFileSystemWatcher
1831              * will delete the watch, and will ignore the file, even after it is recreated.  Since it is impossible
1832              * to reliably detect this case, always re-request the watch on a dirty signal, to avoid losing the
1833              * underlying OS monitor.
1834              */
1835             fsWatcher->addPath(e->path);
1836         }
1837     }
1838 }
1839 #else
fswEventReceived(const QString & path)1840 void KDirWatchPrivate::fswEventReceived(const QString &path)
1841 {
1842     Q_UNUSED(path);
1843     qCWarning(KCOREADDONS_DEBUG) << "QFileSystemWatcher event received but QFileSystemWatcher is not supported";
1844 }
1845 #endif // HAVE_QFILESYSTEMWATCHER
1846 
1847 //
1848 // Class KDirWatch
1849 //
1850 
Q_GLOBAL_STATIC(KDirWatch,s_pKDirWatchSelf)1851 Q_GLOBAL_STATIC(KDirWatch, s_pKDirWatchSelf)
1852 KDirWatch *KDirWatch::self()
1853 {
1854     return s_pKDirWatchSelf();
1855 }
1856 
1857 // <steve> is this used anywhere?
1858 // <dfaure> yes, see kio/src/core/kcoredirlister_p.h:328
exists()1859 bool KDirWatch::exists()
1860 {
1861     return s_pKDirWatchSelf.exists() && dwp_self.hasLocalData();
1862 }
1863 
postRoutine_KDirWatch()1864 static void postRoutine_KDirWatch()
1865 {
1866     if (s_pKDirWatchSelf.exists()) {
1867         s_pKDirWatchSelf()->deleteQFSWatcher();
1868     }
1869 }
1870 
KDirWatch(QObject * parent)1871 KDirWatch::KDirWatch(QObject *parent)
1872     : QObject(parent)
1873     , d(createPrivate())
1874 {
1875     d->ref();
1876     static QBasicAtomicInt nameCounter = Q_BASIC_ATOMIC_INITIALIZER(1);
1877     const int counter = nameCounter.fetchAndAddRelaxed(1); // returns the old value
1878     setObjectName(QStringLiteral("KDirWatch-%1").arg(counter));
1879 
1880     if (counter == 1) { // very first KDirWatch instance
1881         // Must delete QFileSystemWatcher before qApp is gone - bug 261541
1882         qAddPostRoutine(postRoutine_KDirWatch);
1883     }
1884 }
1885 
~KDirWatch()1886 KDirWatch::~KDirWatch()
1887 {
1888     if (d && dwp_self.hasLocalData()) { // skip this after app destruction
1889         d->removeEntries(this);
1890         d->unref();
1891     }
1892 }
1893 
addDir(const QString & _path,WatchModes watchModes)1894 void KDirWatch::addDir(const QString &_path, WatchModes watchModes)
1895 {
1896     if (KNetworkMounts::self()->isOptionEnabledForPath(_path, KNetworkMounts::KDirWatchDontAddWatches)) {
1897         return;
1898     }
1899 
1900     if (d) {
1901         d->addEntry(this, _path, nullptr, true, watchModes);
1902     }
1903 }
1904 
addFile(const QString & _path)1905 void KDirWatch::addFile(const QString &_path)
1906 {
1907     if (KNetworkMounts::self()->isOptionEnabledForPath(_path, KNetworkMounts::KDirWatchDontAddWatches)) {
1908         return;
1909     }
1910 
1911     if (!d) {
1912         return;
1913     }
1914 
1915     d->addEntry(this, _path, nullptr, false);
1916 }
1917 
ctime(const QString & _path) const1918 QDateTime KDirWatch::ctime(const QString &_path) const
1919 {
1920     KDirWatchPrivate::Entry *e = d->entry(_path);
1921 
1922     if (!e) {
1923         return QDateTime();
1924     }
1925 
1926     return QDateTime::fromSecsSinceEpoch(e->m_ctime);
1927 }
1928 
removeDir(const QString & _path)1929 void KDirWatch::removeDir(const QString &_path)
1930 {
1931     if (d) {
1932         d->removeEntry(this, _path, nullptr);
1933     }
1934 }
1935 
removeFile(const QString & _path)1936 void KDirWatch::removeFile(const QString &_path)
1937 {
1938     if (d) {
1939         d->removeEntry(this, _path, nullptr);
1940     }
1941 }
1942 
stopDirScan(const QString & _path)1943 bool KDirWatch::stopDirScan(const QString &_path)
1944 {
1945     if (d) {
1946         KDirWatchPrivate::Entry *e = d->entry(_path);
1947         if (e && e->isDir) {
1948             return d->stopEntryScan(this, e);
1949         }
1950     }
1951     return false;
1952 }
1953 
restartDirScan(const QString & _path)1954 bool KDirWatch::restartDirScan(const QString &_path)
1955 {
1956     if (d) {
1957         KDirWatchPrivate::Entry *e = d->entry(_path);
1958         if (e && e->isDir)
1959         // restart without notifying pending events
1960         {
1961             return d->restartEntryScan(this, e, false);
1962         }
1963     }
1964     return false;
1965 }
1966 
stopScan()1967 void KDirWatch::stopScan()
1968 {
1969     if (d) {
1970         d->stopScan(this);
1971         d->_isStopped = true;
1972     }
1973 }
1974 
isStopped()1975 bool KDirWatch::isStopped()
1976 {
1977     return d->_isStopped;
1978 }
1979 
startScan(bool notify,bool skippedToo)1980 void KDirWatch::startScan(bool notify, bool skippedToo)
1981 {
1982     if (d) {
1983         d->_isStopped = false;
1984         d->startScan(this, notify, skippedToo);
1985     }
1986 }
1987 
contains(const QString & _path) const1988 bool KDirWatch::contains(const QString &_path) const
1989 {
1990     KDirWatchPrivate::Entry *e = d->entry(_path);
1991     if (!e) {
1992         return false;
1993     }
1994 
1995     for (const KDirWatchPrivate::Client &client : e->m_clients) {
1996         if (client.instance == this) {
1997             return true;
1998         }
1999     }
2000 
2001     return false;
2002 }
2003 
deleteQFSWatcher()2004 void KDirWatch::deleteQFSWatcher()
2005 {
2006     delete d->fsWatcher;
2007     d->fsWatcher = nullptr;
2008     d = nullptr;
2009 }
2010 
statistics()2011 void KDirWatch::statistics()
2012 {
2013     if (!dwp_self.hasLocalData()) {
2014         qCDebug(KDIRWATCH) << "KDirWatch not used";
2015         return;
2016     }
2017     dwp_self.localData()->statistics();
2018 }
2019 
setCreated(const QString & _file)2020 void KDirWatch::setCreated(const QString &_file)
2021 {
2022     qCDebug(KDIRWATCH) << objectName() << "emitting created" << _file;
2023     Q_EMIT created(_file);
2024 }
2025 
setDirty(const QString & _file)2026 void KDirWatch::setDirty(const QString &_file)
2027 {
2028     Q_EMIT dirty(_file);
2029 }
2030 
setDeleted(const QString & _file)2031 void KDirWatch::setDeleted(const QString &_file)
2032 {
2033     qCDebug(KDIRWATCH) << objectName() << "emitting deleted" << _file;
2034     Q_EMIT deleted(_file);
2035 }
2036 
internalMethod() const2037 KDirWatch::Method KDirWatch::internalMethod() const
2038 {
2039     // This reproduces the logic in KDirWatchPrivate::addWatch
2040     switch (d->m_preferredMethod) {
2041     case KDirWatch::FAM:
2042 #if HAVE_FAM
2043         if (d->use_fam) {
2044             return KDirWatch::FAM;
2045         }
2046 #endif
2047         break;
2048     case KDirWatch::INotify:
2049 #if HAVE_SYS_INOTIFY_H
2050         if (d->supports_inotify) {
2051             return KDirWatch::INotify;
2052         }
2053 #endif
2054         break;
2055     case KDirWatch::QFSWatch:
2056 #if HAVE_QFILESYSTEMWATCHER
2057         return KDirWatch::QFSWatch;
2058 #else
2059         break;
2060 #endif
2061     case KDirWatch::Stat:
2062         return KDirWatch::Stat;
2063     }
2064 
2065 #if HAVE_SYS_INOTIFY_H
2066     if (d->supports_inotify) {
2067         return KDirWatch::INotify;
2068     }
2069 #endif
2070 #if HAVE_FAM
2071     if (d->use_fam) {
2072         return KDirWatch::FAM;
2073     }
2074 #endif
2075 #if HAVE_QFILESYSTEMWATCHER
2076     return KDirWatch::QFSWatch;
2077 #else
2078     return KDirWatch::Stat;
2079 #endif
2080 }
2081 
2082 #include "moc_kdirwatch.cpp"
2083 #include "moc_kdirwatch_p.cpp"
2084 
2085 // sven
2086