• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.10.5 API Reference
  • KDE Home
  • Contact Us
 

KDECore

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

KDE's Doxygen guidelines are available online.

KDECore

Skip menu "KDECore"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Modules
  • Related Pages

kdelibs-4.10.5 API Reference

Skip menu "kdelibs-4.10.5 API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal