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

KDE's Doxygen guidelines are available online.