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

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
  •     tdecore
  • tdeabc
  • tdecmshell
  • tdecore
  • tdefx
  • tdehtml
  • tdeinit
  • tdeio
  •   bookmarks
  •   httpfilter
  •   kpasswdserver
  •   kssl
  • tdeioslave
  •   http
  •   tdefile
  •   tdeio
  •   tdeioexec
  • tdemdi
  •   tdemdi
  • tdenewstuff
  • tdeparts
  • tdeprint
  • tderandr
  • tderesources
  • tdespell2
  • tdesu
  • tdeui
  • tdeunittest
  • tdeutils
  • tdewallet
Generated for tdeio/tdeio by doxygen 1.8.8
This website is maintained by Timothy Pearson.