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