• Skip to content
  • Skip to link menu
Trinity API Reference
  • Trinity API Reference
  • tdeio/tdeio
 

tdeio/tdeio

  • tdeio
  • tdeio
kdirwatch.cpp
1 /* This file is part of the KDE libraries
2  Copyright (C) 1998 Sven Radej <sven@lisa.exp.univie.ac.at>
3 
4  This library is free software; you can redistribute it and/or
5  modify it under the terms of the GNU Library General Public
6  License version 2 as published by the Free Software Foundation.
7 
8  This library is distributed in the hope that it will be useful,
9  but WITHOUT ANY WARRANTY; without even the implied warranty of
10  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11  Library General Public License for more details.
12 
13  You should have received a copy of the GNU Library General Public License
14  along with this library; see the file COPYING.LIB. If not, write to
15  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16  Boston, MA 02110-1301, USA.
17 */
18 
19 
20 // CHANGES:
21 // Oct 4, 2005 - Inotify support (Dirk Mueller)
22 // Februar 2002 - Add file watching and remote mount check for STAT
23 // Mar 30, 2001 - Native support for Linux dir change notification.
24 // Jan 28, 2000 - Usage of FAM service on IRIX (Josef.Weidendorfer@in.tum.de)
25 // May 24. 1998 - List of times introduced, and some bugs are fixed. (sven)1
26 // May 23. 1998 - Removed static pointer - you can have more instances.
27 // It was Needed for KRegistry. KDirWatch now emits signals and doesn't
28 // call (or need) KFM. No more URL's - just plain paths. (sven)
29 // Mar 29. 1998 - added docs, stop/restart for particular Dirs and
30 // deep copies for list of dirs. (sven)
31 // Mar 28. 1998 - Created. (sven)
32 
33 
34 #include <config.h>
35 #include <errno.h>
36 
37 #ifdef HAVE_DNOTIFY
38 #include <unistd.h>
39 #include <time.h>
40 #include <fcntl.h>
41 #include <signal.h>
42 #include <errno.h>
43 #endif
44 
45 
46 #include <sys/stat.h>
47 #include <assert.h>
48 #include <tqdir.h>
49 #include <tqfile.h>
50 #include <tqintdict.h>
51 #include <tqptrlist.h>
52 #include <tqsocketnotifier.h>
53 #include <tqstringlist.h>
54 #include <tqtimer.h>
55 
56 #include <tdeapplication.h>
57 #include <kdebug.h>
58 #include <tdeconfig.h>
59 #include <tdeglobal.h>
60 #include <kstaticdeleter.h>
61 #include <kde_file.h>
62 #include <kurl.h>
63 
64 // debug
65 #include <sys/ioctl.h>
66 
67 #ifdef Q_OS_SOLARIS
68 #include <sys/filio.h> /* FIONREAD */
69 #endif /* solaris */
70 
71 #ifdef HAVE_INOTIFY
72 #include <unistd.h>
73 #include <fcntl.h>
74 #include <sys/syscall.h>
75 #ifdef Q_OS_LINUX
76 #include <linux/types.h>
77 #endif /* Linux */
78 // Linux kernel headers are documented to not compile
79 #define _S390_BITOPS_H
80 #include <sys/inotify.h>
81 
82 #ifndef __NR_inotify_init
83 #if defined(__i386__)
84 #define __NR_inotify_init 291
85 #define __NR_inotify_add_watch 292
86 #define __NR_inotify_rm_watch 293
87 #endif
88 #if defined(__PPC__)
89 #define __NR_inotify_init 275
90 #define __NR_inotify_add_watch 276
91 #define __NR_inotify_rm_watch 277
92 #endif
93 #if defined(__x86_64__)
94 #define __NR_inotify_init 253
95 #define __NR_inotify_add_watch 254
96 #define __NR_inotify_rm_watch 255
97 #endif
98 #endif
99 
100 #ifndef IN_ONLYDIR
101 #define IN_ONLYDIR 0x01000000
102 #endif
103 
104 #ifndef IN_DONT_FOLLOW
105 #define IN_DONT_FOLLOW 0x02000000
106 #endif
107 
108 #ifndef IN_MOVE_SELF
109 #define IN_MOVE_SELF 0x00000800
110 #endif
111 
112 #endif
113 
114 #include <sys/utsname.h>
115 
116 #include "kdirwatch.h"
117 #include "kdirwatch_p.h"
118 #include "global.h" // TDEIO::probably_slow_mounted
119 
120 #define NO_NOTIFY (time_t) 0
121 
122 static KDirWatchPrivate* dwp_self = 0;
123 
124 #ifdef HAVE_DNOTIFY
125 
126 static int dnotify_signal = 0;
127 
128 /* DNOTIFY signal handler
129  *
130  * As this is called asynchronously, only a flag is set and
131  * a rescan is requested.
132  * This is done by writing into a pipe to trigger a TQSocketNotifier
133  * watching on this pipe: a timer is started and after a timeout,
134  * the rescan is done.
135  */
136 void KDirWatchPrivate::dnotify_handler(int, siginfo_t *si, void *)
137 {
138  if (!dwp_self) return;
139 
140  // write might change errno, we have to save it and restore it
141  // (Richard Stevens, Advanced programming in the Unix Environment)
142  int saved_errno = errno;
143 
144  Entry* e = dwp_self->fd_Entry.find(si->si_fd);
145 
146 // kdDebug(7001) << "DNOTIFY Handler: fd " << si->si_fd << " path "
147 // << TQString(e ? e->path:"unknown") << endl;
148 
149  if(e && e->dn_fd == si->si_fd)
150  e->dirty = true;
151 
152  char c = 0;
153  write(dwp_self->mPipe[1], &c, 1);
154  errno = saved_errno;
155 }
156 
157 static struct sigaction old_sigio_act;
158 /* DNOTIFY SIGIO signal handler
159  *
160  * When the kernel queue for the dnotify_signal overflows, a SIGIO is send.
161  */
162 void KDirWatchPrivate::dnotify_sigio_handler(int sig, siginfo_t *si, void *p)
163 {
164  if (dwp_self)
165  {
166  // write might change errno, we have to save it and restore it
167  // (Richard Stevens, Advanced programming in the Unix Environment)
168  int saved_errno = errno;
169 
170  dwp_self->rescan_all = true;
171  char c = 0;
172  write(dwp_self->mPipe[1], &c, 1);
173 
174  errno = saved_errno;
175  }
176 
177  // Call previous signal handler
178  if (old_sigio_act.sa_flags & SA_SIGINFO)
179  {
180  if (old_sigio_act.sa_sigaction)
181  (*old_sigio_act.sa_sigaction)(sig, si, p);
182  }
183  else
184  {
185  if ((old_sigio_act.sa_handler != SIG_DFL) &&
186  (old_sigio_act.sa_handler != SIG_IGN))
187  (*old_sigio_act.sa_handler)(sig);
188  }
189 }
190 #endif
191 
192 
193 //
194 // Class KDirWatchPrivate (singleton)
195 //
196 
197 /* All entries (files/directories) to be watched in the
198  * application (coming from multiple KDirWatch instances)
199  * are registered in a single KDirWatchPrivate instance.
200  *
201  * At the moment, the following methods for file watching
202  * are supported:
203  * - Polling: All files to be watched are polled regularly
204  * using stat (more precise: TQFileInfo.lastModified()).
205  * The polling frequency is determined from global tdeconfig
206  * settings, defaulting to 500 ms for local directories
207  * and 5000 ms for remote mounts
208  * - FAM (File Alteration Monitor): first used on IRIX, SGI
209  * has ported this method to LINUX. It uses a kernel part
210  * (IMON, sending change events to /dev/imon) and a user
211  * level damon (fam), to which applications connect for
212  * notification of file changes. For NFS, the fam damon
213  * on the NFS server machine is used; if IMON is not built
214  * into the kernel, fam uses polling for local files.
215  * - DNOTIFY: In late LINUX 2.3.x, directory notification was
216  * introduced. By opening a directory, you can request for
217  * UNIX signals to be sent to the process when a directory
218  * is changed.
219  * - INOTIFY: In LINUX 2.6.13, inode change notification was
220  * introduced. You're now able to watch arbitrary inode's
221  * for changes, and even get notification when they're
222  * unmounted.
223  */
224 
225 KDirWatchPrivate::KDirWatchPrivate()
226  : rescan_timer(0, "KDirWatchPrivate::rescan_timer")
227 {
228  timer = new TQTimer(this, "KDirWatchPrivate::timer");
229  connect (timer, TQ_SIGNAL(timeout()), this, TQ_SLOT(slotRescan()));
230  freq = 3600000; // 1 hour as upper bound
231  statEntries = 0;
232  delayRemove = false;
233  m_ref = 0;
234 
235  TDEConfigGroup config(TDEGlobal::config(), TQCString("DirWatch"));
236  m_nfsPollInterval = config.readNumEntry("NFSPollInterval", 5000);
237  m_PollInterval = config.readNumEntry("PollInterval", 500);
238 
239  TQString available("Stat");
240 
241  // used for FAM and DNOTIFY
242  rescan_all = false;
243  connect(&rescan_timer, TQ_SIGNAL(timeout()), this, TQ_SLOT(slotRescan()));
244 
245 #ifdef HAVE_FAM
246  // It's possible that FAM server can't be started
247  if (FAMOpen(&fc) ==0) {
248  available += ", FAM";
249  use_fam=true;
250  sn = new TQSocketNotifier( FAMCONNECTION_GETFD(&fc),
251  TQSocketNotifier::Read, this);
252  connect( sn, TQ_SIGNAL(activated(int)),
253  this, TQ_SLOT(famEventReceived()) );
254  }
255  else {
256  kdDebug(7001) << "Can't use FAM (fam daemon not running?)" << endl;
257  use_fam=false;
258  }
259 #endif
260 
261 #ifdef HAVE_INOTIFY
262  supports_inotify = true;
263 
264  m_inotify_fd = inotify_init();
265 
266  if ( m_inotify_fd <= 0 ) {
267  kdDebug(7001) << "Can't use Inotify, kernel doesn't support it" << endl;
268  supports_inotify = false;
269  }
270 
271  {
272  struct utsname uts;
273  int major, minor, patch;
274  if (uname(&uts) < 0)
275  supports_inotify = false; // *shrug*
276  else if (sscanf(uts.release, "%d.%d.%d", &major, &minor, &patch) != 3)
277  supports_inotify = false; // *shrug*
278  else if( major * 1000000 + minor * 1000 + patch < 2006014 ) { // <2.6.14
279  kdDebug(7001) << "Can't use INotify, Linux kernel too old" << endl;
280  supports_inotify = false;
281  }
282  }
283 
284  if ( supports_inotify ) {
285  available += ", Inotify";
286  fcntl(m_inotify_fd, F_SETFD, FD_CLOEXEC);
287 
288  mSn = new TQSocketNotifier( m_inotify_fd, TQSocketNotifier::Read, this );
289  connect( mSn, TQ_SIGNAL(activated( int )), this, TQ_SLOT( slotActivated() ) );
290  }
291 #endif
292 
293 #ifdef HAVE_DNOTIFY
294 
295  // if we have inotify, disable dnotify.
296 #ifdef HAVE_INOTIFY
297  supports_dnotify = !supports_inotify;
298 #else
299  // otherwise, not guilty until proven guilty.
300  supports_dnotify = true;
301 #endif
302 
303  struct utsname uts;
304  int major, minor, patch;
305  if (uname(&uts) < 0)
306  supports_dnotify = false; // *shrug*
307  else if (sscanf(uts.release, "%d.%d.%d", &major, &minor, &patch) != 3)
308  supports_dnotify = false; // *shrug*
309  else if( major * 1000000 + minor * 1000 + patch < 2004019 ) { // <2.4.19
310  kdDebug(7001) << "Can't use DNotify, Linux kernel too old" << endl;
311  supports_dnotify = false;
312  }
313 
314  if( supports_dnotify ) {
315  available += ", DNotify";
316 
317  pipe(mPipe);
318  fcntl(mPipe[0], F_SETFD, FD_CLOEXEC);
319  fcntl(mPipe[1], F_SETFD, FD_CLOEXEC);
320  fcntl(mPipe[0], F_SETFL, O_NONBLOCK | fcntl(mPipe[0], F_GETFL));
321  fcntl(mPipe[1], F_SETFL, O_NONBLOCK | fcntl(mPipe[1], F_GETFL));
322  mSn = new TQSocketNotifier( mPipe[0], TQSocketNotifier::Read, this);
323  connect(mSn, TQ_SIGNAL(activated(int)), this, TQ_SLOT(slotActivated()));
324  // Install the signal handler only once
325  if ( dnotify_signal == 0 )
326  {
327  dnotify_signal = SIGRTMIN + 8;
328 
329  struct sigaction act;
330  act.sa_sigaction = KDirWatchPrivate::dnotify_handler;
331  sigemptyset(&act.sa_mask);
332  act.sa_flags = SA_SIGINFO;
333 #ifdef SA_RESTART
334  act.sa_flags |= SA_RESTART;
335 #endif
336  sigaction(dnotify_signal, &act, NULL);
337 
338  act.sa_sigaction = KDirWatchPrivate::dnotify_sigio_handler;
339  sigaction(SIGIO, &act, &old_sigio_act);
340  }
341  }
342  else
343  {
344  mPipe[0] = -1;
345  mPipe[1] = -1;
346  }
347 #endif
348 
349  kdDebug(7001) << "Available methods: " << available << endl;
350 }
351 
352 /* This is called on app exit (KStaticDeleter) */
353 KDirWatchPrivate::~KDirWatchPrivate()
354 {
355  timer->stop();
356 
357  /* remove all entries being watched */
358  removeEntries(0);
359 
360 #ifdef HAVE_FAM
361  if (use_fam) {
362  FAMClose(&fc);
363  kdDebug(7001) << "KDirWatch deleted (FAM closed)" << endl;
364  }
365 #endif
366 #ifdef HAVE_INOTIFY
367  if ( supports_inotify )
368  ::close( m_inotify_fd );
369 #endif
370 #ifdef HAVE_DNOTIFY
371  close(mPipe[0]);
372  close(mPipe[1]);
373 #endif
374 }
375 
376 #include <stdlib.h>
377 
378 void KDirWatchPrivate::slotActivated()
379 {
380 #ifdef HAVE_DNOTIFY
381  if ( supports_dnotify )
382  {
383  char dummy_buf[4096];
384  read(mPipe[0], &dummy_buf, 4096);
385 
386  if (!rescan_timer.isActive())
387  rescan_timer.start(m_PollInterval, true /* singleshot */);
388 
389  return;
390  }
391 #endif
392 
393 #ifdef HAVE_INOTIFY
394  if ( !supports_inotify )
395  return;
396 
397  int pending = -1;
398  int offset = 0;
399  char buf[4096];
400  assert( m_inotify_fd > -1 );
401  ioctl( m_inotify_fd, FIONREAD, &pending );
402 
403  while ( pending > 0 ) {
404 
405  if ( pending > (int)sizeof( buf ) )
406  pending = sizeof( buf );
407 
408  pending = read( m_inotify_fd, buf, pending);
409 
410  while ( pending > 0 ) {
411  struct inotify_event *event = (struct inotify_event *) &buf[offset];
412  pending -= sizeof( struct inotify_event ) + event->len;
413  offset += sizeof( struct inotify_event ) + event->len;
414 
415  TQString path;
416  if ( event->len )
417  path = TQFile::decodeName( TQCString( event->name, event->len ) );
418 
419  if ( path.length() && isNoisyFile( path.latin1() ) )
420  continue;
421 
422  kdDebug(7001) << "ev wd: " << event->wd << " mask " << event->mask << " path: " << path << endl;
423 
424  // now we're in deep trouble of finding the
425  // associated entries
426  // for now, we suck and iterate
427  for ( EntryMap::Iterator it = m_mapEntries.begin();
428  it != m_mapEntries.end(); ++it ) {
429  Entry* e = &( *it );
430  if ( e->wd == event->wd ) {
431  e->dirty = true;
432 
433  if ( 1 || e->isDir) {
434  if( event->mask & IN_DELETE_SELF) {
435  kdDebug(7001) << "-->got deleteself signal for " << e->path << endl;
436  e->m_status = NonExistent;
437  if (e->isDir)
438  addEntry(0, TQDir::cleanDirPath(e->path.path()+"/.."), e, true);
439  else
440  addEntry(0, TQFileInfo(e->path.path()).dirPath(true), e, true);
441  }
442  if ( event->mask & IN_IGNORED ) {
443  e->wd = 0;
444  }
445  if ( event->mask & (IN_CREATE|IN_MOVED_TO) ) {
446  Entry *sub_entry = e->m_entries.first();
447  for(;sub_entry; sub_entry = e->m_entries.next())
448  if (sub_entry->path == e->path.path() + "/" + path) break;
449 
450  if (sub_entry /*&& sub_entry->isDir*/) {
451  removeEntry(0,e->path.path(), sub_entry);
452  KDE_struct_stat stat_buf;
453  TQCString tpath = TQFile::encodeName(path);
454  KDE_stat(tpath, &stat_buf);
455 
456  //sub_entry->isDir = S_ISDIR(stat_buf.st_mode);
457  //sub_entry->m_ctime = stat_buf.st_ctime;
458  //sub_entry->m_status = Normal;
459  //sub_entry->m_nlink = stat_buf.st_nlink;
460 
461  if(!useINotify(sub_entry))
462  useStat(sub_entry);
463  sub_entry->dirty = true;
464  }
465  }
466  }
467 
468  if (!rescan_timer.isActive())
469  rescan_timer.start(m_PollInterval, true /* singleshot */);
470 
471  break; // there really should be only one matching wd
472  }
473  }
474 
475  }
476  }
477 #endif
478 }
479 
480 /* In DNOTIFY/FAM mode, only entries which are marked dirty are scanned.
481  * We first need to mark all yet nonexistent, but possible created
482  * entries as dirty...
483  */
484 void KDirWatchPrivate::Entry::propagate_dirty()
485 {
486  for (TQPtrListIterator<Entry> sub_entry (m_entries);
487  sub_entry.current(); ++sub_entry)
488  {
489  if (!sub_entry.current()->dirty)
490  {
491  sub_entry.current()->dirty = true;
492  sub_entry.current()->propagate_dirty();
493  }
494  }
495 }
496 
497 
498 /* A KDirWatch instance is interested in getting events for
499  * this file/Dir entry.
500  */
501 void KDirWatchPrivate::Entry::addClient(KDirWatch* instance)
502 {
503  Client* client = m_clients.first();
504  for(;client; client = m_clients.next())
505  if (client->instance == instance) break;
506 
507  if (client) {
508  client->count++;
509  return;
510  }
511 
512  client = new Client;
513  client->instance = instance;
514  client->count = 1;
515  client->watchingStopped = instance->isStopped();
516  client->pending = NoChange;
517 
518  m_clients.append(client);
519 }
520 
521 void KDirWatchPrivate::Entry::removeClient(KDirWatch* instance)
522 {
523  Client* client = m_clients.first();
524  for(;client; client = m_clients.next())
525  if (client->instance == instance) break;
526 
527  if (client) {
528  client->count--;
529  if (client->count == 0) {
530  m_clients.removeRef(client);
531  delete client;
532  }
533  }
534 }
535 
536 /* get number of clients */
537 int KDirWatchPrivate::Entry::clients()
538 {
539  int clients = 0;
540  Client* client = m_clients.first();
541  for(;client; client = m_clients.next())
542  clients += client->count;
543 
544  return clients;
545 }
546 
547 
548 KDirWatchPrivate::Entry* KDirWatchPrivate::entry(const KURL& _path)
549 {
550 // we only support absolute paths
551  if (TQDir::isRelativePath(_path.path())) {
552  return 0;
553  }
554 
555  TQString path = _path.path();
556 
557  if ( path.length() > 1 && path.right(1) == "/" )
558  path.truncate( path.length() - 1 );
559 
560  EntryMap::Iterator it = m_mapEntries.find( _path );
561  if ( it == m_mapEntries.end() )
562  return 0;
563  else
564  return &(*it);
565 }
566 
567 // set polling frequency for a entry and adjust global freq if needed
568 void KDirWatchPrivate::useFreq(Entry* e, int newFreq)
569 {
570  e->freq = newFreq;
571 
572  // a reasonable frequency for the global polling timer
573  if (e->freq < freq) {
574  freq = e->freq;
575  if (timer->isActive()) timer->changeInterval(freq);
576  kdDebug(7001) << "Global Poll Freq is now " << freq << " msec" << endl;
577  }
578 }
579 
580 
581 #ifdef HAVE_FAM
582 // setup FAM notification, returns false if not possible
583 bool KDirWatchPrivate::useFAM(Entry* e)
584 {
585  if (!use_fam) return false;
586 
587  // handle FAM events to avoid deadlock
588  // (FAM sends back all files in a directory when monitoring)
589  famEventReceived();
590 
591  e->m_mode = FAMMode;
592  e->dirty = false;
593 
594  if (e->isDir) {
595  if (e->m_status == NonExistent) {
596  // If the directory does not exist we watch the parent directory
597  addEntry(0, TQDir::cleanDirPath(e->path.path()+"/.."), e, true);
598  }
599  else {
600  int res =FAMMonitorDirectory(&fc, TQFile::encodeName(e->path.path()),
601  &(e->fr), e);
602  if (res<0) {
603  e->m_mode = UnknownMode;
604  use_fam=false;
605  return false;
606  }
607  kdDebug(7001) << " Setup FAM (Req "
608  << FAMREQUEST_GETREQNUM(&(e->fr))
609  << ") for " << e->path.path() << endl;
610  }
611  }
612  else {
613  if (e->m_status == NonExistent) {
614  // If the file does not exist we watch the directory
615  addEntry(0, TQFileInfo(e->path.path()).dirPath(true), e, true);
616  }
617  else {
618  int res = FAMMonitorFile(&fc, TQFile::encodeName(e->path.path()),
619  &(e->fr), e);
620  if (res<0) {
621  e->m_mode = UnknownMode;
622  use_fam=false;
623  return false;
624  }
625 
626  kdDebug(7001) << " Setup FAM (Req "
627  << FAMREQUEST_GETREQNUM(&(e->fr))
628  << ") for " << e->path.path() << endl;
629  }
630  }
631 
632  // handle FAM events to avoid deadlock
633  // (FAM sends back all files in a directory when monitoring)
634  famEventReceived();
635 
636  return true;
637 }
638 #endif
639 
640 
641 #ifdef HAVE_DNOTIFY
642 // setup DNotify notification, returns false if not possible
643 bool KDirWatchPrivate::useDNotify(Entry* e)
644 {
645  e->dn_fd = 0;
646  e->dirty = false;
647  if (!supports_dnotify) return false;
648 
649  e->m_mode = DNotifyMode;
650 
651  if (e->isDir) {
652  if (e->m_status == Normal) {
653  int fd = KDE_open(TQFile::encodeName(e->path.path()).data(), O_RDONLY);
654  // Migrate fd to somewhere above 128. Some libraries have
655  // constructs like:
656  // fd = socket(...)
657  // if (fd > ARBITRARY_LIMIT)
658  // return error;
659  //
660  // Since programs might end up using a lot of KDirWatch objects
661  // for a rather long time the above braindamage could get
662  // triggered.
663  //
664  // By moving the kdirwatch fd's to > 128, calls like socket() will keep
665  // returning fd's < ARBITRARY_LIMIT for a bit longer.
666  int fd2 = fcntl(fd, F_DUPFD, 128);
667  if (fd2 >= 0)
668  {
669  close(fd);
670  fd = fd2;
671  }
672  if (fd<0) {
673  e->m_mode = UnknownMode;
674  return false;
675  }
676 
677  int mask = DN_DELETE|DN_CREATE|DN_RENAME|DN_MULTISHOT;
678  // if dependant is a file watch, we check for MODIFY & ATTRIB too
679  for(Entry* dep=e->m_entries.first();dep;dep=e->m_entries.next())
680  if (!dep->isDir) { mask |= DN_MODIFY|DN_ATTRIB; break; }
681 
682  if(fcntl(fd, F_SETSIG, dnotify_signal) < 0 ||
683  fcntl(fd, F_NOTIFY, mask) < 0) {
684 
685  kdDebug(7001) << "Not using Linux Directory Notifications."
686  << endl;
687  supports_dnotify = false;
688  ::close(fd);
689  e->m_mode = UnknownMode;
690  return false;
691  }
692 
693  fd_Entry.replace(fd, e);
694  e->dn_fd = fd;
695 
696  kdDebug(7001) << " Setup DNotify (fd " << fd
697  << ") for " << e->path.path() << endl;
698  }
699  else { // NotExisting
700  addEntry(0, TQDir::cleanDirPath(e->path.path()+"/.."), e, true);
701  }
702  }
703  else { // File
704  // we always watch the directory (DNOTIFY can't watch files alone)
705  // this notifies us about changes of files therein
706  addEntry(0, TQFileInfo(e->path.path()).dirPath(true), e, true);
707  }
708 
709  return true;
710 }
711 #endif
712 
713 #ifdef HAVE_INOTIFY
714 // setup INotify notification, returns false if not possible
715 bool KDirWatchPrivate::useINotify( Entry* e )
716 {
717  e->wd = 0;
718  e->dirty = false;
719  if (!supports_inotify) return false;
720 
721  e->m_mode = INotifyMode;
722 
723  int mask = IN_DELETE|IN_DELETE_SELF|IN_CREATE|IN_MOVE|IN_MOVE_SELF|IN_DONT_FOLLOW;
724  if(!e->isDir)
725  mask |= IN_MODIFY|IN_ATTRIB;
726  else
727  mask |= IN_ONLYDIR;
728 
729  // if dependant is a file watch, we check for MODIFY & ATTRIB too
730  for(Entry* dep=e->m_entries.first();dep;dep=e->m_entries.next()) {
731  if (!dep->isDir) { mask |= IN_MODIFY|IN_ATTRIB; break; }
732  }
733 
734  if ( ( e->wd = inotify_add_watch( m_inotify_fd,
735  TQFile::encodeName( e->path.path() ), mask) ) > 0 )
736  return true;
737 
738  if ( e->m_status == NonExistent ) {
739  if (e->isDir)
740  addEntry(0, TQDir::cleanDirPath(e->path.path()+"/.."), e, true);
741  else
742  addEntry(0, TQFileInfo(e->path.path()).dirPath(true), e, true);
743  return true;
744  }
745 
746  return false;
747 }
748 #endif
749 
750 bool KDirWatchPrivate::useStat(Entry* e)
751 {
752  if ( e->path.path().startsWith("/media/") || e->path.path().startsWith("/run/") || (e->path.path() == "/media")
753  || (TDEIO::probably_slow_mounted(e->path.path())) )
754  useFreq(e, m_nfsPollInterval);
755  else
756  useFreq(e, m_PollInterval);
757 
758  if (e->m_mode != StatMode) {
759  e->m_mode = StatMode;
760  statEntries++;
761 
762  if ( statEntries == 1 ) {
763  // if this was first STAT entry (=timer was stopped)
764  timer->start(freq); // then start the timer
765  kdDebug(7001) << " Started Polling Timer, freq " << freq << endl;
766  }
767  }
768 
769  kdDebug(7001) << " Setup Stat (freq " << e->freq
770  << ") for " << e->path.path() << endl;
771 
772  return true;
773 }
774 
775 
776 /* If <instance> !=0, this KDirWatch instance wants to watch at <_path>,
777  * providing in <isDir> the type of the entry to be watched.
778  * Sometimes, entries are dependant on each other: if <sub_entry> !=0,
779  * this entry needs another entry to watch himself (when notExistent).
780  */
781 void KDirWatchPrivate::addEntry(KDirWatch* instance, const KURL& _path,
782  Entry* sub_entry, bool isDir)
783 {
784  TQString path = _path.path();
785  if (path.startsWith("/dev/") || (path == "/dev"))
786  return; // Don't even go there.
787 
788  if ( path.length() > 1 && path.right(1) == "/" ) {
789  path.truncate( path.length() - 1 );
790  }
791 
792  EntryMap::Iterator it = m_mapEntries.find( _path );
793  if ( it != m_mapEntries.end() )
794  {
795  if (sub_entry) {
796  (*it).m_entries.append(sub_entry);
797  kdDebug(7001) << "Added already watched Entry " << path
798  << " (for " << sub_entry->path << ")" << endl;
799 
800 #ifdef HAVE_DNOTIFY
801  {
802  Entry* e = &(*it);
803  if( (e->m_mode == DNotifyMode) && (e->dn_fd > 0) ) {
804  int mask = DN_DELETE|DN_CREATE|DN_RENAME|DN_MULTISHOT;
805  // if dependant is a file watch, we check for MODIFY & ATTRIB too
806  for(Entry* dep=e->m_entries.first();dep;dep=e->m_entries.next())
807  if (!dep->isDir) { mask |= DN_MODIFY|DN_ATTRIB; break; }
808  if( fcntl(e->dn_fd, F_NOTIFY, mask) < 0) { // shouldn't happen
809  ::close(e->dn_fd);
810  e->m_mode = UnknownMode;
811  fd_Entry.remove(e->dn_fd);
812  e->dn_fd = 0;
813  useStat( e );
814  }
815  }
816  }
817 #endif
818 
819 #ifdef HAVE_INOTIFY
820  {
821  Entry* e = &(*it);
822  if( (e->m_mode == INotifyMode) && (e->wd > 0) ) {
823  int mask = IN_DELETE|IN_DELETE_SELF|IN_CREATE|IN_MOVE|IN_MOVE_SELF|IN_DONT_FOLLOW;
824  if(!e->isDir)
825  mask |= IN_MODIFY|IN_ATTRIB;
826  else
827  mask |= IN_ONLYDIR;
828 
829  inotify_rm_watch (m_inotify_fd, e->wd);
830  e->wd = inotify_add_watch( m_inotify_fd, TQFile::encodeName( e->path.path() ), mask);
831  }
832  }
833 #endif
834 
835  }
836  else {
837  (*it).addClient(instance);
838  kdDebug(7001) << "Added already watched Entry " << path
839  << " (now " << (*it).clients() << " clients)"
840  << TQString(TQString(" [%1]").arg(instance->name())) << endl;
841  }
842  return;
843  }
844 
845  // we have a new path to watch
846 
847  KDE_struct_stat stat_buf;
848  TQCString tpath = TQFile::encodeName(path);
849  bool exists = (KDE_stat(tpath, &stat_buf) == 0);
850 
851  Entry newEntry;
852  m_mapEntries.insert( _path, newEntry );
853  // the insert does a copy, so we have to use <e> now
854  Entry* e = &(m_mapEntries[_path]);
855 
856  if (exists) {
857  e->isDir = S_ISDIR(stat_buf.st_mode);
858 
859  if (e->isDir && !isDir)
860  kdWarning() << "KDirWatch: " << path << " is a directory. Use addDir!" << endl;
861  else if (!e->isDir && isDir)
862  kdWarning() << "KDirWatch: " << path << " is a file. Use addFile!" << endl;
863 
864  e->m_ctime = stat_buf.st_ctime;
865  e->m_mtime = stat_buf.st_mtime;
866  e->m_status = Normal;
867  e->m_nlink = stat_buf.st_nlink;
868  }
869  else {
870  e->isDir = isDir;
871  e->m_ctime = invalid_ctime;
872  e->m_mtime = invalid_mtime;
873  e->m_status = NonExistent;
874  e->m_nlink = 0;
875  }
876 
877  e->path = _path;
878  if (sub_entry)
879  e->m_entries.append(sub_entry);
880  else
881  e->addClient(instance);
882 
883  kdDebug(7001) << "Added " << (e->isDir ? "Dir ":"File ") << path
884  << (e->m_status == NonExistent ? " NotExisting" : "")
885  << (sub_entry ? TQString(TQString(" for %1").arg(sub_entry->path.path())) : TQString(""))
886  << (instance ? TQString(TQString(" [%1]").arg(instance->name())) : TQString(""))
887  << endl;
888 
889 
890  // now setup the notification method
891  e->m_mode = UnknownMode;
892  e->msecLeft = 0;
893 
894  if ( isNoisyFile( tpath ) ) {
895  return;
896  }
897 
898 #ifdef HAVE_FAM
899  if (useFAM(e)) return;
900 #endif
901 
902 #ifdef HAVE_INOTIFY
903  if (useINotify(e)) return;
904 #endif
905 
906 #ifdef HAVE_DNOTIFY
907  if (useDNotify(e)) return;
908 #endif
909 
910  useStat(e);
911 }
912 
913 
914 void KDirWatchPrivate::removeEntry( KDirWatch* instance,
915  const KURL& _path, Entry* sub_entry )
916 {
917  kdDebug(7001) << "KDirWatchPrivate::removeEntry for '" << _path << "' sub_entry: " << sub_entry << endl;
918  Entry* e = entry(_path);
919  if (!e) {
920  kdDebug(7001) << "KDirWatchPrivate::removeEntry can't handle '" << _path << "'" << endl;
921  return;
922  }
923 
924  if (sub_entry)
925  e->m_entries.removeRef(sub_entry);
926  else
927  e->removeClient(instance);
928 
929  if (e->m_clients.count() || e->m_entries.count()) {
930  kdDebug(7001) << "removeEntry: unwatched " << e->path.path() << " " << _path << endl;
931  return;
932  }
933 
934  if (delayRemove) {
935  // removeList is allowed to contain any entry at most once
936  if (removeList.findRef(e)==-1)
937  removeList.append(e);
938  // now e->isValid() is false
939  return;
940  }
941 
942 #ifdef HAVE_FAM
943  if (e->m_mode == FAMMode) {
944  if ( e->m_status == Normal) {
945  FAMCancelMonitor(&fc, &(e->fr) );
946  kdDebug(7001) << "Cancelled FAM (Req "
947  << FAMREQUEST_GETREQNUM(&(e->fr))
948  << ") for " << e->path.path() << endl;
949  }
950  else {
951  if (e->isDir)
952  removeEntry(0, TQDir::cleanDirPath(e->path.path()+"/.."), e);
953  else
954  removeEntry(0, TQFileInfo(e->path.path()).dirPath(true), e);
955  }
956  }
957 #endif
958 
959 #ifdef HAVE_INOTIFY
960  kdDebug(7001) << "inotify remove " << ( e->m_mode == INotifyMode ) << " " << ( e->m_status == Normal ) << endl;
961  if (e->m_mode == INotifyMode) {
962  if ( e->m_status == Normal ) {
963  (void) inotify_rm_watch( m_inotify_fd, e->wd );
964  kdDebug(7001) << "Cancelled INotify (fd " <<
965  m_inotify_fd << ", " << e->wd <<
966  ") for " << e->path.path() << endl;
967  }
968  else {
969  if (e->isDir)
970  removeEntry(0, TQDir::cleanDirPath(e->path.path()+"/.."), e);
971  else
972  removeEntry(0, TQFileInfo(e->path.path()).dirPath(true), e);
973  }
974  }
975 #endif
976 
977 #ifdef HAVE_DNOTIFY
978  if (e->m_mode == DNotifyMode) {
979  if (!e->isDir) {
980  removeEntry(0, TQFileInfo(e->path.path()).dirPath(true), e);
981  }
982  else { // isDir
983  // must close the FD.
984  if ( e->m_status == Normal) {
985  if (e->dn_fd) {
986  ::close(e->dn_fd);
987  fd_Entry.remove(e->dn_fd);
988 
989  kdDebug(7001) << "Cancelled DNotify (fd " << e->dn_fd
990  << ") for " << e->path.path() << endl;
991  e->dn_fd = 0;
992 
993  }
994  }
995  else {
996  removeEntry(0, TQDir::cleanDirPath(e->path.path()+"/.."), e);
997  }
998  }
999  }
1000 #endif
1001 
1002  if (e->m_mode == StatMode) {
1003  statEntries--;
1004  if ( statEntries == 0 ) {
1005  timer->stop(); // stop timer if lists are empty
1006  kdDebug(7001) << " Stopped Polling Timer" << endl;
1007  }
1008  }
1009 
1010  kdDebug(7001) << "Removed " << (e->isDir ? "Dir ":"File ") << e->path.path()
1011  << (sub_entry ? TQString(TQString(" for %1").arg(sub_entry->path.path())) : TQString(""))
1012  << (instance ? TQString(TQString(" [%1]").arg(instance->name())) : TQString(""))
1013  << endl;
1014  m_mapEntries.remove( e->path ); // <e> not valid any more
1015 }
1016 
1017 
1018 /* Called from KDirWatch destructor:
1019  * remove <instance> as client from all entries
1020  */
1021 void KDirWatchPrivate::removeEntries( KDirWatch* instance )
1022 {
1023  TQPtrList<Entry> list;
1024  int minfreq = 3600000;
1025 
1026  // put all entries where instance is a client in list
1027  EntryMap::Iterator it = m_mapEntries.begin();
1028  for( ; it != m_mapEntries.end(); ++it ) {
1029  Client* c = (*it).m_clients.first();
1030  for(;c;c=(*it).m_clients.next())
1031  if (c->instance == instance) break;
1032  if (c) {
1033  c->count = 1; // forces deletion of instance as client
1034  list.append(&(*it));
1035  }
1036  else if ( (*it).m_mode == StatMode && (*it).freq < minfreq )
1037  minfreq = (*it).freq;
1038  }
1039 
1040  for(Entry* e=list.first();e;e=list.next())
1041  removeEntry(instance, e->path, 0);
1042 
1043  if (minfreq > freq) {
1044  // we can decrease the global polling frequency
1045  freq = minfreq;
1046  if (timer->isActive()) timer->changeInterval(freq);
1047  kdDebug(7001) << "Poll Freq now " << freq << " msec" << endl;
1048  }
1049 }
1050 
1051 // instance ==0: stop scanning for all instances
1052 bool KDirWatchPrivate::stopEntryScan( KDirWatch* instance, Entry* e)
1053 {
1054  int stillWatching = 0;
1055  Client* c = e->m_clients.first();
1056  for(;c;c=e->m_clients.next()) {
1057  if (!instance || instance == c->instance)
1058  c->watchingStopped = true;
1059  else if (!c->watchingStopped)
1060  stillWatching += c->count;
1061  }
1062 
1063  kdDebug(7001) << instance->name() << " stopped scanning " << e->path.path()
1064  << " (now " << stillWatching << " watchers)" << endl;
1065 
1066  if (stillWatching == 0) {
1067  // if nobody is interested, we don't watch
1068  e->m_ctime = invalid_ctime; // invalid
1069  e->m_mtime = invalid_mtime; // invalid
1070  e->m_status = NonExistent;
1071  // e->m_status = Normal;
1072  }
1073  return true;
1074 }
1075 
1076 // instance ==0: start scanning for all instances
1077 bool KDirWatchPrivate::restartEntryScan( KDirWatch* instance, Entry* e,
1078  bool notify)
1079 {
1080  int wasWatching = 0, newWatching = 0;
1081  Client* c = e->m_clients.first();
1082  for(;c;c=e->m_clients.next()) {
1083  if (!c->watchingStopped)
1084  wasWatching += c->count;
1085  else if (!instance || instance == c->instance) {
1086  c->watchingStopped = false;
1087  newWatching += c->count;
1088  }
1089  }
1090  if (newWatching == 0)
1091  return false;
1092 
1093  kdDebug(7001) << (instance ? instance->name() : "all") << " restarted scanning " << e->path.path()
1094  << " (now " << wasWatching+newWatching << " watchers)" << endl;
1095 
1096  // restart watching and emit pending events
1097 
1098  int ev = NoChange;
1099  if (wasWatching == 0) {
1100  if (!notify) {
1101  KDE_struct_stat stat_buf;
1102  bool exists = (KDE_stat(TQFile::encodeName(e->path.path()), &stat_buf) == 0);
1103  if (exists) {
1104  e->m_ctime = stat_buf.st_ctime;
1105  e->m_mtime = stat_buf.st_mtime;
1106  e->m_status = Normal;
1107  e->m_nlink = stat_buf.st_nlink;
1108  }
1109  else {
1110  e->m_ctime = invalid_ctime;
1111  e->m_mtime = invalid_mtime;
1112  e->m_status = NonExistent;
1113  e->m_nlink = 0;
1114  }
1115  }
1116  e->msecLeft = 0;
1117  ev = scanEntry(e);
1118  }
1119  emitEvent(e,ev);
1120 
1121  return true;
1122 }
1123 
1124 // instance ==0: stop scanning for all instances
1125 void KDirWatchPrivate::stopScan(KDirWatch* instance)
1126 {
1127  EntryMap::Iterator it = m_mapEntries.begin();
1128  for( ; it != m_mapEntries.end(); ++it )
1129  stopEntryScan(instance, &(*it));
1130 }
1131 
1132 
1133 void KDirWatchPrivate::startScan(KDirWatch* instance,
1134  bool notify, bool skippedToo )
1135 {
1136  if (!notify)
1137  resetList(instance,skippedToo);
1138 
1139  EntryMap::Iterator it = m_mapEntries.begin();
1140  for( ; it != m_mapEntries.end(); ++it )
1141  restartEntryScan(instance, &(*it), notify);
1142 
1143  // timer should still be running when in polling mode
1144 }
1145 
1146 
1147 // clear all pending events, also from stopped
1148 void KDirWatchPrivate::resetList( KDirWatch* /*instance*/,
1149  bool skippedToo )
1150 {
1151  EntryMap::Iterator it = m_mapEntries.begin();
1152  for( ; it != m_mapEntries.end(); ++it ) {
1153 
1154  Client* c = (*it).m_clients.first();
1155  for(;c;c=(*it).m_clients.next())
1156  if (!c->watchingStopped || skippedToo)
1157  c->pending = NoChange;
1158  }
1159 }
1160 
1161 // Return event happened on <e>
1162 //
1163 int KDirWatchPrivate::scanEntry(Entry* e)
1164 {
1165 #ifdef HAVE_FAM
1166  if (e->m_mode == FAMMode) {
1167  // we know nothing has changed, no need to stat
1168  if(!e->dirty) return NoChange;
1169  e->dirty = false;
1170  }
1171  if (e->isDir) return Changed;
1172 #endif
1173 
1174  // Shouldn't happen: Ignore "unknown" notification method
1175  if (e->m_mode == UnknownMode) return NoChange;
1176 
1177 #if defined ( HAVE_DNOTIFY ) || defined( HAVE_INOTIFY )
1178  if (e->m_mode == DNotifyMode || e->m_mode == INotifyMode ) {
1179  // we know nothing has changed, no need to stat
1180  if(!e->dirty) return NoChange;
1181  kdDebug(7001) << "scanning " << e->path.path() << " " << e->m_status << " " << e->m_ctime << " " << e->m_mtime << endl;
1182  e->dirty = false;
1183  }
1184 #endif
1185 
1186  if (e->m_mode == StatMode) {
1187  // only scan if timeout on entry timer happens;
1188  // e.g. when using 500msec global timer, a entry
1189  // with freq=5000 is only watched every 10th time
1190 
1191  e->msecLeft -= freq;
1192  if (e->msecLeft>0) return NoChange;
1193  e->msecLeft += e->freq;
1194  }
1195 
1196  KDE_struct_stat stat_buf;
1197  bool exists = (KDE_stat(TQFile::encodeName(e->path.path()), &stat_buf) == 0);
1198  if (exists) {
1199 
1200  if (e->m_status == NonExistent) {
1201  // ctime is the 'creation time' on windows, but with qMax
1202  // we get the latest change of any kind, on any platform.
1203  e->m_ctime = stat_buf.st_ctime;
1204  e->m_mtime = stat_buf.st_mtime;
1205  e->m_status = Normal;
1206  e->m_nlink = stat_buf.st_nlink;
1207  return Created;
1208  }
1209 
1210  if ( (e->m_ctime != invalid_ctime) &&
1211  ((stat_buf.st_ctime != e->m_ctime) ||
1212  (stat_buf.st_mtime != e->m_mtime) ||
1213  (stat_buf.st_nlink != (nlink_t) e->m_nlink)) ) {
1214  e->m_ctime = stat_buf.st_ctime;
1215  e->m_mtime = stat_buf.st_mtime;
1216  e->m_nlink = stat_buf.st_nlink;
1217  return Changed;
1218  }
1219 
1220  return NoChange;
1221  }
1222 
1223  // dir/file doesn't exist
1224 
1225  if (e->m_ctime == invalid_ctime && e->m_status == NonExistent) {
1226  e->m_nlink = 0;
1227  e->m_status = NonExistent;
1228  return NoChange;
1229  }
1230 
1231  e->m_ctime = invalid_ctime;
1232  e->m_mtime = invalid_mtime;
1233  e->m_nlink = 0;
1234  e->m_status = NonExistent;
1235 
1236  return Deleted;
1237 }
1238 
1239 /* Notify all interested KDirWatch instances about a given event on an entry
1240  * and stored pending events. When watching is stopped, the event is
1241  * added to the pending events.
1242  */
1243 void KDirWatchPrivate::emitEvent(Entry* e, int event, const KURL &fileName)
1244 {
1245  TQString path = e->path.path();
1246  if (!fileName.isEmpty()) {
1247  if (!TQDir::isRelativePath(fileName.path()))
1248  path = fileName.path();
1249  else
1250 #ifdef Q_OS_UNIX
1251  path += "/" + fileName.path();
1252 #elif defined(TQ_WS_WIN)
1253  //current drive is passed instead of /
1254  path += TQDir::currentDirPath().left(2) + "/" + fileName.path();
1255 #endif
1256  }
1257 
1258  TQPtrListIterator<Client> cit( e->m_clients );
1259  for ( ; cit.current(); ++cit )
1260  {
1261  Client* c = cit.current();
1262 
1263  if (c->instance==0 || c->count==0) continue;
1264 
1265  if (c->watchingStopped) {
1266  // add event to pending...
1267  if (event == Changed)
1268  c->pending |= event;
1269  else if (event == Created || event == Deleted)
1270  c->pending = event;
1271  continue;
1272  }
1273  // not stopped
1274  if (event == NoChange || event == Changed)
1275  event |= c->pending;
1276  c->pending = NoChange;
1277  if (event == NoChange) continue;
1278 
1279  if (event & Deleted) {
1280  c->instance->setDeleted(path);
1281  // emit only Deleted event...
1282  continue;
1283  }
1284 
1285  if (event & Created) {
1286  c->instance->setCreated(path);
1287  // possible emit Change event after creation
1288  }
1289 
1290  if (event & Changed) {
1291  c->instance->setDirty(path);
1292  c->instance->setDirty(e->path);
1293  }
1294  }
1295 }
1296 
1297 // Remove entries which were marked to be removed
1298 void KDirWatchPrivate::slotRemoveDelayed()
1299 {
1300  Entry* e;
1301  delayRemove = false;
1302  for(e=removeList.first();e;e=removeList.next())
1303  removeEntry(0, e->path, 0);
1304  removeList.clear();
1305 }
1306 
1307 /* Scan all entries to be watched for changes. This is done regularly
1308  * when polling and once after a DNOTIFY signal. This is NOT used by FAM.
1309  */
1310 void KDirWatchPrivate::slotRescan()
1311 {
1312  EntryMap::Iterator it;
1313 
1314  // People can do very long things in the slot connected to dirty(),
1315  // like showing a message box. We don't want to keep polling during
1316  // that time, otherwise the value of 'delayRemove' will be reset.
1317  bool timerRunning = timer->isActive();
1318  if ( timerRunning ) {
1319  timer->stop();
1320  }
1321 
1322  // We delay deletions of entries this way.
1323  // removeDir(), when called in slotDirty(), can cause a crash otherwise
1324  delayRemove = true;
1325 
1326 #if defined(HAVE_DNOTIFY) || defined(HAVE_INOTIFY)
1327  TQPtrList<Entry> dList, cList;
1328 #endif
1329 
1330  if (rescan_all)
1331  {
1332  // mark all as dirty
1333  it = m_mapEntries.begin();
1334  for( ; it != m_mapEntries.end(); ++it ) {
1335  (*it).dirty = true;
1336  }
1337  rescan_all = false;
1338  }
1339  else
1340  {
1341  // progate dirty flag to dependant entries (e.g. file watches)
1342  it = m_mapEntries.begin();
1343  for( ; it != m_mapEntries.end(); ++it ) {
1344  if (((*it).m_mode == INotifyMode || (*it).m_mode == DNotifyMode) && (*it).dirty ) {
1345  (*it).propagate_dirty();
1346  }
1347  }
1348  }
1349 
1350  it = m_mapEntries.begin();
1351  for( ; it != m_mapEntries.end(); ++it ) {
1352  // we don't check invalid entries (i.e. remove delayed)
1353  if (!(*it).isValid()) continue;
1354 
1355  int ev = scanEntry( &(*it) );
1356 
1357 
1358 #ifdef HAVE_INOTIFY
1359  if ((*it).m_mode == INotifyMode && ev == Created && (*it).wd == 0) {
1360  cList.append( &(*it) );
1361  if (! useINotify( &(*it) )) {
1362  useStat( &(*it) );
1363  }
1364  }
1365 #endif
1366 
1367 #ifdef HAVE_DNOTIFY
1368  if ((*it).m_mode == DNotifyMode) {
1369  if ((*it).isDir && (ev == Deleted)) {
1370  dList.append( &(*it) );
1371 
1372  // must close the FD.
1373  if ((*it).dn_fd) {
1374  ::close((*it).dn_fd);
1375  fd_Entry.remove((*it).dn_fd);
1376  (*it).dn_fd = 0;
1377  }
1378  }
1379 
1380  else if ((*it).isDir && (ev == Created)) {
1381  // For created, but yet without DNOTIFYing ...
1382  if ( (*it).dn_fd == 0) {
1383  cList.append( &(*it) );
1384  if (! useDNotify( &(*it) )) {
1385  // if DNotify setup fails...
1386  useStat( &(*it) );
1387  }
1388  }
1389  }
1390  }
1391 #endif
1392 
1393  if ( ev != NoChange ) {
1394  // Emit events for any entries with the same path as the changed entry
1395  EntryMap::Iterator it2;
1396  it2 = m_mapEntries.begin();
1397  for( ; it2 != m_mapEntries.end(); ++it2 ) {
1398  if ((*it).path.url() == (*it2).path.url()) {
1399  emitEvent( &(*it2), ev);
1400  }
1401  }
1402  }
1403  }
1404 
1405 
1406 #if defined(HAVE_DNOTIFY) || defined(HAVE_INOTIFY)
1407  // Scan parent of deleted directories for new creation
1408  Entry* e;
1409  for(e=dList.first();e;e=dList.next()) {
1410  addEntry(0, TQDir::cleanDirPath( e->path.path()+"/.."), e, true);
1411  }
1412 
1413  // Remove watch of parent of new created directories
1414  for(e=cList.first();e;e=cList.next()) {
1415  removeEntry(0, TQDir::cleanDirPath( e->path.path()+"/.."), e);
1416  }
1417 #endif
1418 
1419  if ( timerRunning ) {
1420  timer->start(freq);
1421  }
1422 
1423  TQTimer::singleShot(0, this, TQ_SLOT(slotRemoveDelayed()));
1424 }
1425 
1426 bool KDirWatchPrivate::isNoisyFile( const char * filename )
1427 {
1428  // $HOME/.X.err grows with debug output, so don't notify change
1429  if ( *filename == '.') {
1430  if (strncmp(filename, ".X.err", 6) == 0) return true;
1431  if (strncmp(filename, ".xsession-errors", 16) == 0) return true;
1432  // fontconfig updates the cache on every KDE app start
1433  // (inclusive tdeio_thumbnail slaves)
1434  if (strncmp(filename, ".fonts.cache", 12) == 0) return true;
1435  }
1436 
1437  return false;
1438 }
1439 
1440 #ifdef HAVE_FAM
1441 void KDirWatchPrivate::famEventReceived()
1442 {
1443  static FAMEvent fe;
1444 
1445  delayRemove = true;
1446 
1447  while(use_fam && FAMPending(&fc)) {
1448  if (FAMNextEvent(&fc, &fe) == -1) {
1449  kdWarning(7001) << "FAM connection problem, switching to polling."
1450  << endl;
1451  use_fam = false;
1452  delete sn; sn = 0;
1453 
1454  // Replace all FAMMode entries with DNotify/Stat
1455  EntryMap::Iterator it;
1456  it = m_mapEntries.begin();
1457  for( ; it != m_mapEntries.end(); ++it )
1458  if ((*it).m_mode == FAMMode && (*it).m_clients.count()>0) {
1459 #ifdef HAVE_INOTIFY
1460  if (useINotify( &(*it) )) continue;
1461 #endif
1462 #ifdef HAVE_DNOTIFY
1463  if (useDNotify( &(*it) )) continue;
1464 #endif
1465  useStat( &(*it) );
1466  }
1467  }
1468  else
1469  checkFAMEvent(&fe);
1470  }
1471 
1472  TQTimer::singleShot(0, this, TQ_SLOT(slotRemoveDelayed()));
1473 }
1474 
1475 void KDirWatchPrivate::checkFAMEvent(FAMEvent* fe)
1476 {
1477  // Don't be too verbose ;-)
1478  if ((fe->code == FAMExists) ||
1479  (fe->code == FAMEndExist) ||
1480  (fe->code == FAMAcknowledge)) return;
1481 
1482  if ( isNoisyFile( fe->filename ) )
1483  return;
1484 
1485  Entry* e = 0;
1486  EntryMap::Iterator it = m_mapEntries.begin();
1487  for( ; it != m_mapEntries.end(); ++it )
1488  if (FAMREQUEST_GETREQNUM(&( (*it).fr )) ==
1489  FAMREQUEST_GETREQNUM(&(fe->fr)) ) {
1490  e = &(*it);
1491  break;
1492  }
1493 
1494  // Entry* e = static_cast<Entry*>(fe->userdata);
1495 
1496 #if 0 // #88538
1497  kdDebug(7001) << "Processing FAM event ("
1498  << ((fe->code == FAMChanged) ? "FAMChanged" :
1499  (fe->code == FAMDeleted) ? "FAMDeleted" :
1500  (fe->code == FAMStartExecuting) ? "FAMStartExecuting" :
1501  (fe->code == FAMStopExecuting) ? "FAMStopExecuting" :
1502  (fe->code == FAMCreated) ? "FAMCreated" :
1503  (fe->code == FAMMoved) ? "FAMMoved" :
1504  (fe->code == FAMAcknowledge) ? "FAMAcknowledge" :
1505  (fe->code == FAMExists) ? "FAMExists" :
1506  (fe->code == FAMEndExist) ? "FAMEndExist" : "Unknown Code")
1507  << ", " << fe->filename
1508  << ", Req " << FAMREQUEST_GETREQNUM(&(fe->fr))
1509  << ")" << endl;
1510 #endif
1511 
1512  if (!e) {
1513  // this happens e.g. for FAMAcknowledge after deleting a dir...
1514  // kdDebug(7001) << "No entry for FAM event ?!" << endl;
1515  return;
1516  }
1517 
1518  if (e->m_status == NonExistent) {
1519  kdDebug(7001) << "FAM event for nonExistent entry " << e->path.path() << endl;
1520  return;
1521  }
1522 
1523  // Delayed handling. This rechecks changes with own stat calls.
1524  e->dirty = true;
1525  if (!rescan_timer.isActive())
1526  rescan_timer.start(m_PollInterval, true);
1527 
1528  // needed FAM control actions on FAM events
1529  if (e->isDir)
1530  switch (fe->code)
1531  {
1532  case FAMDeleted:
1533  // file absolute: watched dir
1534  if (!TQDir::isRelativePath(fe->filename))
1535  {
1536  // a watched directory was deleted
1537 
1538  e->m_status = NonExistent;
1539  FAMCancelMonitor(&fc, &(e->fr) ); // needed ?
1540  kdDebug(7001) << "Cancelled FAMReq "
1541  << FAMREQUEST_GETREQNUM(&(e->fr))
1542  << " for " << e->path.path() << endl;
1543  // Scan parent for a new creation
1544  addEntry(0, TQDir::cleanDirPath( e->path.path()+"/.."), e, true);
1545  }
1546  break;
1547 
1548  case FAMCreated: {
1549  // check for creation of a directory we have to watch
1550  Entry *sub_entry = e->m_entries.first();
1551  for(;sub_entry; sub_entry = e->m_entries.next())
1552  if (sub_entry->path.path() == e->path.path() + "/" + fe->filename) break;
1553  if (sub_entry && sub_entry->isDir) {
1554  KURL path = e->path;
1555  removeEntry(0,e->path,sub_entry); // <e> can be invalid here!!
1556  sub_entry->m_status = Normal;
1557  if (!useFAM(sub_entry))
1558  {
1559 #ifdef HAVE_INOTIFY
1560  if (!useINotify(sub_entry ))
1561 #endif
1562  {
1563  useStat(sub_entry);
1564  }
1565  }
1566  }
1567  break;
1568  }
1569 
1570  default:
1571  break;
1572  }
1573 }
1574 #else
1575 void KDirWatchPrivate::famEventReceived() {}
1576 #endif
1577 
1578 
1579 void KDirWatchPrivate::statistics()
1580 {
1581  EntryMap::Iterator it;
1582 
1583  kdDebug(7001) << "Entries watched:" << endl;
1584  if (m_mapEntries.count()==0) {
1585  kdDebug(7001) << " None." << endl;
1586  }
1587  else {
1588  it = m_mapEntries.begin();
1589  for( ; it != m_mapEntries.end(); ++it ) {
1590  Entry* e = &(*it);
1591  kdDebug(7001) << " " << e->path.path() << " ("
1592  << ((e->m_status==Normal)?"":"Nonexistent ")
1593  << (e->isDir ? "Dir":"File") << ", using "
1594  << ((e->m_mode == FAMMode) ? "FAM" :
1595  (e->m_mode == INotifyMode) ? "INotify" :
1596  (e->m_mode == DNotifyMode) ? "DNotify" :
1597  (e->m_mode == StatMode) ? "Stat" : "Unknown Method")
1598  << ")" << endl;
1599 
1600  Client* c = e->m_clients.first();
1601  for(;c; c = e->m_clients.next()) {
1602  TQString pending;
1603  if (c->watchingStopped) {
1604  if (c->pending & Deleted) pending += "deleted ";
1605  if (c->pending & Created) pending += "created ";
1606  if (c->pending & Changed) pending += "changed ";
1607  if (!pending.isEmpty()) pending = " (pending: " + pending + ")";
1608  pending = ", stopped" + pending;
1609  }
1610  kdDebug(7001) << " by " << c->instance->name()
1611  << " (" << c->count << " times)"
1612  << pending << endl;
1613  }
1614  if (e->m_entries.count()>0) {
1615  kdDebug(7001) << " dependent entries:" << endl;
1616  Entry* d = e->m_entries.first();
1617  for(;d; d = e->m_entries.next()) {
1618  kdDebug(7001) << " " << d << endl;
1619  kdDebug(7001) << " " << d->path << " (" << d << ") " << endl;
1620  }
1621  }
1622  }
1623  }
1624 }
1625 
1626 
1627 //
1628 // Class KDirWatch
1629 //
1630 
1631 static KStaticDeleter<KDirWatch> sd_dw;
1632 KDirWatch* KDirWatch::s_pSelf = 0L;
1633 
1634 KDirWatch* KDirWatch::self()
1635 {
1636  if ( !s_pSelf ) {
1637  sd_dw.setObject( s_pSelf, new KDirWatch );
1638  }
1639 
1640  return s_pSelf;
1641 }
1642 
1643 bool KDirWatch::exists()
1644 {
1645  return s_pSelf != 0;
1646 }
1647 
1648 KDirWatch::KDirWatch (TQObject* parent, const char* name)
1649  : TQObject(parent,name)
1650 {
1651  if (!name) {
1652  static int nameCounter = 0;
1653 
1654  nameCounter++;
1655  setName(TQString(TQString("KDirWatch-%1").arg(nameCounter)).ascii());
1656  }
1657 
1658  if (!dwp_self)
1659  dwp_self = new KDirWatchPrivate;
1660  d = dwp_self;
1661  d->ref();
1662 
1663  _isStopped = false;
1664 }
1665 
1666 KDirWatch::~KDirWatch()
1667 {
1668  d->removeEntries(this);
1669  if ( d->deref() )
1670  {
1671  // delete it if it's the last one
1672  delete d;
1673  dwp_self = 0L;
1674  }
1675 }
1676 
1677 
1678 // TODO: add watchFiles/recursive support
1679 void KDirWatch::addDir( const TQString& _path, bool watchFiles, bool recursive)
1680 {
1681  if (watchFiles || recursive) {
1682  kdDebug(7001) << "addDir - recursive/watchFiles not supported yet in TDE 3.x" << endl;
1683  }
1684  if (d) d->addEntry(this, _path, 0, true);
1685 }
1686 
1687 // TODO: add watchFiles/recursive support
1688 void KDirWatch::addDir( const KURL& _url, bool watchFiles, bool recursive)
1689 {
1690  if (watchFiles || recursive) {
1691  kdDebug(7001) << "addDir - recursive/watchFiles not supported yet in TDE 3.x" << endl;
1692  }
1693  if (d) d->addEntry(this, _url, 0, true);
1694 }
1695 
1696 void KDirWatch::addFile( const TQString& _path )
1697 {
1698  if (d) d->addEntry(this, _path, 0, false);
1699 }
1700 
1701 TQDateTime KDirWatch::ctime( const TQString &_path )
1702 {
1703  KDirWatchPrivate::Entry* e = d->entry(_path);
1704 
1705  if (!e)
1706  return TQDateTime();
1707 
1708  TQDateTime result;
1709  result.setTime_t(e->m_ctime);
1710  return result;
1711 }
1712 
1713 void KDirWatch::removeDir( const TQString& _path )
1714 {
1715  if (d) d->removeEntry(this, _path, 0);
1716 }
1717 
1718 void KDirWatch::removeDir( const KURL& _url )
1719 {
1720  if (d) d->removeEntry(this, _url, 0);
1721 }
1722 
1723 void KDirWatch::removeFile( const TQString& _path )
1724 {
1725  if (d) d->removeEntry(this, _path, 0);
1726 }
1727 
1728 bool KDirWatch::stopDirScan( const TQString& _path )
1729 {
1730  if (d) {
1731  KDirWatchPrivate::Entry *e = d->entry(_path);
1732  if (e && e->isDir) return d->stopEntryScan(this, e);
1733  }
1734  return false;
1735 }
1736 
1737 bool KDirWatch::restartDirScan( const TQString& _path )
1738 {
1739  if (d) {
1740  KDirWatchPrivate::Entry *e = d->entry(_path);
1741  if (e && e->isDir)
1742  // restart without notifying pending events
1743  return d->restartEntryScan(this, e, false);
1744  }
1745  return false;
1746 }
1747 
1748 void KDirWatch::stopScan()
1749 {
1750  if (d) d->stopScan(this);
1751  _isStopped = true;
1752 }
1753 
1754 void KDirWatch::startScan( bool notify, bool skippedToo )
1755 {
1756  _isStopped = false;
1757  if (d) d->startScan(this, notify, skippedToo);
1758 }
1759 
1760 
1761 bool KDirWatch::contains( const TQString& _path ) const
1762 {
1763  KDirWatchPrivate::Entry* e = d->entry(_path);
1764  if (!e)
1765  return false;
1766 
1767  KDirWatchPrivate::Client* c = e->m_clients.first();
1768  for(;c;c=e->m_clients.next())
1769  if (c->instance == this) return true;
1770 
1771  return false;
1772 }
1773 
1774 void KDirWatch::statistics()
1775 {
1776  if (!dwp_self) {
1777  kdDebug(7001) << "KDirWatch not used" << endl;
1778  return;
1779  }
1780  dwp_self->statistics();
1781 }
1782 
1783 
1784 void KDirWatch::setCreated( const TQString & _file )
1785 {
1786  kdDebug(7001) << name() << " emitting created " << _file << endl;
1787  emit created( _file );
1788 }
1789 
1790 void KDirWatch::setDirty( const TQString & _file )
1791 {
1792  kdDebug(7001) << name() << " emitting dirty " << _file << endl;
1793  emit dirty( _file );
1794 }
1795 
1796 void KDirWatch::setDirty( const KURL & _url )
1797 {
1798  kdDebug(7001) << name() << " emitting dirty " << _url << endl;
1799  emit dirty( _url );
1800 }
1801 
1802 void KDirWatch::setDeleted( const TQString & _file )
1803 {
1804  kdDebug(7001) << name() << " emitting deleted " << _file << endl;
1805  emit deleted( _file );
1806 }
1807 
1808 KDirWatch::Method KDirWatch::internalMethod()
1809 {
1810 #ifdef HAVE_FAM
1811  if (d->use_fam)
1812  return KDirWatch::FAM;
1813 #endif
1814 #ifdef HAVE_INOTIFY
1815  if (d->supports_inotify)
1816  return KDirWatch::INotify;
1817 #endif
1818 #ifdef HAVE_DNOTIFY
1819  if (d->supports_dnotify)
1820  return KDirWatch::DNotify;
1821 #endif
1822  return KDirWatch::Stat;
1823 }
1824 
1825 
1826 #include "kdirwatch.moc"
1827 #include "kdirwatch_p.moc"
KDirWatch
Watch directories and files for changes.
Definition: kdirwatch.h:66
KDirWatch::created
void created(const TQString &path)
Emitted when a file or directory is created.
KDirWatch::stopScan
void stopScan()
Stops scanning of all directories in internal list.
Definition: kdirwatch.cpp:1748
KDirWatch::ctime
TQDateTime ctime(const TQString &path)
Returns the time the directory/file was last changed.
Definition: kdirwatch.cpp:1701
KDirWatch::restartDirScan
bool restartDirScan(const TQString &path)
Restarts scanning for specified path.
Definition: kdirwatch.cpp:1737
KDirWatch::contains
bool contains(const TQString &path) const
Check if a directory is being watched by this KDirWatch instance.
Definition: kdirwatch.cpp:1761
KDirWatch::removeDir
void removeDir(const TQString &path)
Removes a directory from the list of scanned directories.
Definition: kdirwatch.cpp:1713
KDirWatch::statistics
static void statistics()
Dump statistic information about all KDirWatch instances.
Definition: kdirwatch.cpp:1774
KDirWatch::self
static KDirWatch * self()
The KDirWatch instance usually globally used in an application.
Definition: kdirwatch.cpp:1634
KDirWatch::internalMethod
Method internalMethod()
Returns the preferred internal method to watch for changes.
Definition: kdirwatch.cpp:1808
KDirWatch::setDeleted
void setDeleted(const TQString &path)
Emits deleted().
Definition: kdirwatch.cpp:1802
KDirWatch::stopDirScan
bool stopDirScan(const TQString &path)
Stops scanning the specified path.
Definition: kdirwatch.cpp:1728
KDirWatch::startScan
void startScan(bool notify=false, bool skippedToo=false)
Starts scanning of all dirs in list.
Definition: kdirwatch.cpp:1754
KDirWatch::setDirty
void setDirty(const TQString &path)
Emits dirty().
Definition: kdirwatch.cpp:1790
KDirWatch::exists
static bool exists()
Returns true if there is an instance of KDirWatch.
Definition: kdirwatch.cpp:1643
KDirWatch::addFile
void addFile(const TQString &file)
Adds a file to be watched.
Definition: kdirwatch.cpp:1696
KDirWatch::~KDirWatch
~KDirWatch()
Destructor.
Definition: kdirwatch.cpp:1666
KDirWatch::isStopped
bool isStopped()
Is scanning stopped? After creation of a KDirWatch instance, this is false.
Definition: kdirwatch.h:195
KDirWatch::removeFile
void removeFile(const TQString &file)
Removes a file from the list of watched files.
Definition: kdirwatch.cpp:1723
KDirWatch::deleted
void deleted(const TQString &path)
Emitted when a file or directory is deleted.
KDirWatch::addDir
void addDir(const TQString &path, bool watchFiles=false, bool recursive=false)
Adds a directory to be watched.
Definition: kdirwatch.cpp:1679
KDirWatch::KDirWatch
KDirWatch(TQObject *parent=0, const char *name=0)
Constructor.
Definition: kdirwatch.cpp:1648
KDirWatch::dirty
void dirty(const TQString &path)
Emitted when a watched object is changed.
KDirWatch::setCreated
void setCreated(const TQString &path)
Emits created().
Definition: kdirwatch.cpp:1784
TDEIO::probably_slow_mounted
TDEIO_EXPORT bool probably_slow_mounted(const TQString &filename)
Checks if the path belongs to a filesystem that is probably slow.
Definition: global.cpp:1967

tdeio/tdeio

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

tdeio/tdeio

Skip menu "tdeio/tdeio"
  • arts
  • dcop
  • dnssd
  • interfaces
  •   kspeech
  •     interface
  •     library
  •   tdetexteditor
  • kate
  • kded
  • kdoctools
  • kimgio
  • kjs
  • libtdemid
  • libtdescreensaver
  • tdeabc
  • tdecmshell
  • tdecore
  • tdefx
  • tdehtml
  • tdeinit
  • tdeio
  •   bookmarks
  •   httpfilter
  •   kpasswdserver
  •   kssl
  •   tdefile
  •   tdeio
  •   tdeioexec
  • tdeioslave
  •   http
  • tdemdi
  •   tdemdi
  • tdenewstuff
  • tdeparts
  • tdeprint
  • tderandr
  • tderesources
  • tdespell2
  • tdesu
  • tdeui
  • tdeunittest
  • tdeutils
  • tdewallet
Generated for tdeio/tdeio by doxygen 1.9.1
This website is maintained by Timothy Pearson.