KCoreAddons

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

KDE's Doxygen guidelines are available online.