summaryrefslogtreecommitdiffabout
path: root/microkde/kio/kio/kdirwatch.cpp
Unidiff
Diffstat (limited to 'microkde/kio/kio/kdirwatch.cpp') (more/less context) (show whitespace changes)
-rw-r--r--microkde/kio/kio/kdirwatch.cpp1442
1 files changed, 1442 insertions, 0 deletions
diff --git a/microkde/kio/kio/kdirwatch.cpp b/microkde/kio/kio/kdirwatch.cpp
new file mode 100644
index 0000000..98d24e0
--- a/dev/null
+++ b/microkde/kio/kio/kdirwatch.cpp
@@ -0,0 +1,1442 @@
1// -*- c-basic-offset: 2 -*-
2/* This file is part of the KDE libraries
3 Copyright (C) 1998 Sven Radej <sven@lisa.exp.univie.ac.at>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License version 2 as published by the Free Software Foundation.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 Boston, MA 02111-1307, USA.
18*/
19
20/*
21Enhanced Version of the file for platform independent KDE tools.
22Copyright (c) 2004 Ulf Schenk
23
24$Id$
25*/
26
27
28// CHANGES:
29// Februar 2002 - Add file watching and remote mount check for STAT
30// Mar 30, 2001 - Native support for Linux dir change notification.
31// Jan 28, 2000 - Usage of FAM service on IRIX (Josef.Weidendorfer@in.tum.de)
32// May 24. 1998 - List of times introduced, and some bugs are fixed. (sven)
33// May 23. 1998 - Removed static pointer - you can have more instances.
34// It was Needed for KRegistry. KDirWatch now emits signals and doesn't
35// call (or need) KFM. No more URL's - just plain paths. (sven)
36// Mar 29. 1998 - added docs, stop/restart for particular Dirs and
37// deep copies for list of dirs. (sven)
38// Mar 28. 1998 - Created. (sven)
39
40
41//US #include <config.h>
42
43#ifdef HAVE_DNOTIFY
44#include <unistd.h>
45#include <time.h>
46#include <fcntl.h>
47#include <signal.h>
48#include <errno.h>
49#endif
50
51#include <sys/stat.h>
52#include <assert.h>
53#include <qdir.h>
54#include <qfile.h>
55#include <qintdict.h>
56#include <qptrlist.h>
57#include <qsocketnotifier.h>
58#include <qstringlist.h>
59#include <qtimer.h>
60
61#include <kapplication.h>
62#include <kdebug.h>
63#include <kconfig.h>
64#include <kconfigbase.h>
65#include <kglobal.h>
66#include <kstaticdeleter.h>
67
68#include "kdirwatch.h"
69#include "kdirwatch_p.h"
70//US #include "global.h" // KIO::probably_slow_mounted
71
72#define NO_NOTIFY (time_t) 0
73
74static KDirWatchPrivate* dwp_self = 0;
75
76#ifdef HAVE_DNOTIFY
77
78#include <sys/utsname.h>
79
80static int dnotify_signal = 0;
81
82/* DNOTIFY signal handler
83 *
84 * As this is called asynchronously, only a flag is set and
85 * a rescan is requested.
86 * This is done by writing into a pipe to trigger a QSocketNotifier
87 * watching on this pipe: a timer is started and after a timeout,
88 * the rescan is done.
89 */
90void KDirWatchPrivate::dnotify_handler(int, siginfo_t *si, void *)
91{
92 // write might change errno, we have to save it and restore it
93 // (Richard Stevens, Advanced programming in the Unix Environment)
94 int saved_errno = errno;
95
96 Entry* e = (dwp_self) ? dwp_self->fd_Entry.find(si->si_fd) :0;
97
98// kdDebug(7001) << "DNOTIFY Handler: fd " << si->si_fd << " path "
99 // << QString(e ? e->path:"unknown") << endl;
100
101 if(!e || e->dn_fd != si->si_fd) {
102 qDebug("fatal error in KDirWatch");
103 } else
104 e->dn_dirty = true;
105
106 char c = 0;
107 write(dwp_self->mPipe[1], &c, 1);
108 errno = saved_errno;
109}
110
111static struct sigaction old_sigio_act;
112/* DNOTIFY SIGIO signal handler
113 *
114 * When the kernel queue for the dnotify_signal overflows, a SIGIO is send.
115 */
116void KDirWatchPrivate::dnotify_sigio_handler(int sig, siginfo_t *si, void *p)
117{
118 // write might change errno, we have to save it and restore it
119 // (Richard Stevens, Advanced programming in the Unix Environment)
120 int saved_errno = errno;
121
122 if (dwp_self)
123 dwp_self->rescan_all = true;
124
125 char c = 0;
126 write(dwp_self->mPipe[1], &c, 1);
127
128 errno = saved_errno;
129
130 // Call previous signal handler
131 if (old_sigio_act.sa_flags & SA_SIGINFO)
132 {
133 if (old_sigio_act.sa_sigaction)
134 (*old_sigio_act.sa_sigaction)(sig, si, p);
135 }
136 else
137 {
138 if ((old_sigio_act.sa_handler != SIG_DFL) &&
139 (old_sigio_act.sa_handler != SIG_IGN))
140 (*old_sigio_act.sa_handler)(sig);
141 }
142}
143#endif
144
145
146//
147// Class KDirWatchPrivate (singleton)
148//
149
150/* All entries (files/directories) to be watched in the
151 * application (coming from multiple KDirWatch instances)
152 * are registered in a single KDirWatchPrivate instance.
153 *
154 * At the moment, the following methods for file watching
155 * are supported:
156 * - Polling: All files to be watched are polled regularly
157 * using stat (more precise: QFileInfo.lastModified()).
158 * The polling frequency is determined from global kconfig
159 * settings, defaulting to 500 ms for local directories
160 * and 5000 ms for remote mounts
161 * - FAM (File Alternation Monitor): first used on IRIX, SGI
162 * has ported this method to LINUX. It uses a kernel part
163 * (IMON, sending change events to /dev/imon) and a user
164 * level damon (fam), to which applications connect for
165 * notification of file changes. For NFS, the fam damon
166 * on the NFS server machine is used; if IMON is not built
167 * into the kernel, fam uses polling for local files.
168 * - DNOTIFY: In late LINUX 2.3.x, directory notification was
169 * introduced. By opening a directory, you can request for
170 * UNIX signals to be sent to the process when a directory
171 * is changed.
172 */
173
174KDirWatchPrivate::KDirWatchPrivate()
175{
176 timer = new QTimer(this);
177 connect (timer, SIGNAL(timeout()), this, SLOT(slotRescan()));
178 freq = 3600000; // 1 hour as upper bound
179 statEntries = 0;
180 delayRemove = false;
181 m_ref = 0;
182
183//US KConfigGroup config(KGlobal::config(), QCString("DirWatch"));
184//US m_nfsPollInterval = config.readNumEntry("NFSPollInterval", 5000);
185//US m_PollInterval = config.readNumEntry("PollInterval", 500);
186 KConfig *config = KGlobal::config();
187 KConfigGroupSaver saver( config, QCString("DirWatch") );
188
189 m_nfsPollInterval = config->readNumEntry("NFSPollInterval", 5000);
190 m_PollInterval = config->readNumEntry("PollInterval", 500);
191
192
193 QString available("Stat");
194
195#ifdef HAVE_FAM
196 // It's possible that FAM server can't be started
197 if (FAMOpen(&fc) ==0) {
198 available += ", FAM";
199 use_fam=true;
200 sn = new QSocketNotifier( FAMCONNECTION_GETFD(&fc),
201 QSocketNotifier::Read, this);
202 connect( sn, SIGNAL(activated(int)),
203 this, SLOT(famEventReceived()) );
204 }
205 else {
206 kdDebug(7001) << "Can't use FAM (fam daemon not running?)" << endl;
207 use_fam=false;
208 }
209#endif
210
211#ifdef HAVE_DNOTIFY
212 supports_dnotify = true; // not guilty until proven guilty
213 rescan_all = false;
214 struct utsname uts;
215 int major, minor, patch;
216 if (uname(&uts) < 0)
217 supports_dnotify = false; // *shrug*
218 else if (sscanf(uts.release, "%d.%d.%d", &major, &minor, &patch) != 3)
219 supports_dnotify = false; // *shrug*
220 else if( major * 1000000 + minor * 1000 + patch < 2004019 ) { // <2.4.19
221 kdDebug(7001) << "Can't use DNotify, Linux kernel too old" << endl;
222 supports_dnotify = false;
223 }
224
225 if( supports_dnotify ) {
226 available += ", DNotify";
227
228 pipe(mPipe);
229 fcntl(mPipe[0], F_SETFD, FD_CLOEXEC);
230 fcntl(mPipe[1], F_SETFD, FD_CLOEXEC);
231 mSn = new QSocketNotifier( mPipe[0], QSocketNotifier::Read, this);
232 connect(mSn, SIGNAL(activated(int)), this, SLOT(slotActivated()));
233 connect(&mTimer, SIGNAL(timeout()), this, SLOT(slotRescan()));
234 struct sigaction act;
235 act.sa_sigaction = KDirWatchPrivate::dnotify_handler;
236 sigemptyset(&act.sa_mask);
237 act.sa_flags = SA_SIGINFO;
238#ifdef SA_RESTART
239 act.sa_flags |= SA_RESTART;
240#endif
241 if( dnotify_signal == 0 )
242 dnotify_signal = SIGRTMIN + 8;
243 sigaction(dnotify_signal, &act, NULL);
244
245 act.sa_sigaction = KDirWatchPrivate::dnotify_sigio_handler;
246 sigaction(SIGIO, &act, &old_sigio_act);
247 }
248#endif
249
250 kdDebug(7001) << "Available methods: " << available << endl;
251}
252
253/* This should never be called, but doesn't harm */
254KDirWatchPrivate::~KDirWatchPrivate()
255{
256 timer->stop();
257
258 /* remove all entries being watched */
259 removeEntries(0);
260
261#ifdef HAVE_FAM
262 if (use_fam) {
263 FAMClose(&fc);
264 kdDebug(7001) << "KDirWatch deleted (FAM closed)" << endl;
265 }
266#endif
267
268}
269
270#ifdef HAVE_DNOTIFY
271void KDirWatchPrivate::slotActivated()
272{
273 char dummy_buf[100];
274 read(mPipe[0], &dummy_buf, 100);
275
276 if (!mTimer.isActive())
277 mTimer.start(200, true);
278}
279
280/* In DNOTIFY mode, only entries which are marked dirty are scanned.
281 * We first need to mark all yet nonexistant, but possible created
282 * entries as dirty...
283 */
284void KDirWatchPrivate::Entry::propagate_dirty()
285{
286 Entry* sub_entry;
287 for(sub_entry = m_entries.first(); sub_entry; sub_entry = m_entries.next())
288 {
289 if (!sub_entry->dn_dirty)
290 {
291 sub_entry->dn_dirty = true;
292 sub_entry->propagate_dirty();
293 }
294 }
295}
296
297#else // !HAVE_DNOTIFY
298// slots always have to be defined...
299void KDirWatchPrivate::slotActivated() {}
300#endif
301
302/* A KDirWatch instance is interested in getting events for
303 * this file/Dir entry.
304 */
305void KDirWatchPrivate::Entry::addClient(KDirWatch* instance)
306{
307 Client* client = m_clients.first();
308 for(;client; client = m_clients.next())
309 if (client->instance == instance) break;
310
311 if (client) {
312 client->count++;
313 return;
314 }
315
316 client = new Client;
317 client->instance = instance;
318 client->count = 1;
319 client->watchingStopped = instance->isStopped();
320 client->pending = NoChange;
321
322 m_clients.append(client);
323}
324
325void KDirWatchPrivate::Entry::removeClient(KDirWatch* instance)
326{
327 Client* client = m_clients.first();
328 for(;client; client = m_clients.next())
329 if (client->instance == instance) break;
330
331 if (client) {
332 client->count--;
333 if (client->count == 0) {
334 m_clients.removeRef(client);
335 delete client;
336 }
337 }
338}
339
340/* get number of clients */
341int KDirWatchPrivate::Entry::clients()
342{
343 int clients = 0;
344 Client* client = m_clients.first();
345 for(;client; client = m_clients.next())
346 clients += client->count;
347
348 return clients;
349}
350
351
352KDirWatchPrivate::Entry* KDirWatchPrivate::entry(const QString& _path)
353{
354// we only support absolute paths
355 if (_path.left(1) != "/") {
356 return 0;
357 }
358
359 QString path = _path;
360
361 if ( path.length() > 1 && path.right(1) == "/" )
362 path.truncate( path.length() - 1 );
363
364 EntryMap::Iterator it = m_mapEntries.find( path );
365 if ( it == m_mapEntries.end() )
366 return 0;
367 else
368 return &(*it);
369}
370
371// set polling frequency for a entry and adjust global freq if needed
372void KDirWatchPrivate::useFreq(Entry* e, int newFreq)
373{
374 e->freq = newFreq;
375
376 // a reasonable frequency for the global polling timer
377 if (e->freq < freq) {
378 freq = e->freq;
379 if (timer->isActive()) timer->changeInterval(freq);
380 kdDebug(7001) << "Global Poll Freq is now " << freq << " msec" << endl;
381 }
382}
383
384
385#if defined(HAVE_FAM)
386// setup FAM notification, returns false if not possible
387bool KDirWatchPrivate::useFAM(Entry* e)
388{
389 if (!use_fam) return false;
390
391 e->m_mode = FAMMode;
392
393 if (e->isDir) {
394 if (e->m_status == NonExistent) {
395 // If the directory does not exist we watch the parent directory
396 addEntry(0, QDir::cleanDirPath(e->path+"/.."), e, true);
397 }
398 else {
399 int res =FAMMonitorDirectory(&fc, QFile::encodeName(e->path),
400 &(e->fr), e);
401 if (res<0) {
402 e->m_mode = UnknownMode;
403 use_fam=false;
404 return false;
405 }
406 kdDebug(7001) << " Setup FAM (Req "
407 << FAMREQUEST_GETREQNUM(&(e->fr))
408 << ") for " << e->path << endl;
409 }
410 }
411 else {
412 if (e->m_status == NonExistent) {
413 // If the file does not exist we watch the directory
414 addEntry(0, QFileInfo(e->path).dirPath(true), e, true);
415 }
416 else {
417 int res = FAMMonitorFile(&fc, QFile::encodeName(e->path),
418 &(e->fr), e);
419 if (res<0) {
420 e->m_mode = UnknownMode;
421 use_fam=false;
422 return false;
423 }
424
425 kdDebug(7001) << " Setup FAM (Req "
426 << FAMREQUEST_GETREQNUM(&(e->fr))
427 << ") for " << e->path << endl;
428 }
429 }
430
431 // handle FAM events to avoid deadlock
432 // (FAM sends back all files in a directory when monitoring)
433 famEventReceived();
434
435 return true;
436}
437#endif
438
439
440#ifdef HAVE_DNOTIFY
441// setup DNotify notification, returns false if not possible
442bool KDirWatchPrivate::useDNotify(Entry* e)
443{
444 e->dn_fd = 0;
445 if (!supports_dnotify) return false;
446
447 e->m_mode = DNotifyMode;
448
449 if (e->isDir) {
450 e->dn_dirty = false;
451 if (e->m_status == Normal) {
452 int fd = open(QFile::encodeName(e->path).data(), O_RDONLY);
453 // Migrate fd to somewhere above 128. Some libraries have
454 // constructs like:
455 // fd = socket(...)
456 // if (fd > ARBITRARY_LIMIT)
457 // return error;
458 //
459 // Since programs might end up using a lot of KDirWatch objects
460 // for a rather long time the above braindamage could get
461 // triggered.
462 //
463 // By moving the kdirwatch fd's to > 128, calls like socket() will keep
464 // returning fd's < ARBITRARY_LIMIT for a bit longer.
465 int fd2 = fcntl(fd, F_DUPFD, 128);
466 if (fd2 >= 0)
467 {
468 close(fd);
469 fd = fd2;
470 }
471 if (fd<0) {
472 e->m_mode = UnknownMode;
473 return false;
474 }
475
476 int mask = DN_DELETE|DN_CREATE|DN_RENAME|DN_MULTISHOT;
477 // if dependant is a file watch, we check for MODIFY & ATTRIB too
478 for(Entry* dep=e->m_entries.first();dep;dep=e->m_entries.next())
479 if (!dep->isDir) { mask |= DN_MODIFY|DN_ATTRIB; break; }
480
481 if(fcntl(fd, F_SETSIG, dnotify_signal) < 0 ||
482 fcntl(fd, F_NOTIFY, mask) < 0) {
483
484 kdDebug(7001) << "Not using Linux Directory Notifications."
485 << endl;
486 supports_dnotify = false;
487 ::close(fd);
488 e->m_mode = UnknownMode;
489 return false;
490 }
491
492 fd_Entry.replace(fd, e);
493 e->dn_fd = fd;
494
495 kdDebug(7001) << " Setup DNotify (fd " << fd
496 << ") for " << e->path << endl;
497 }
498 else { // NotExisting
499 addEntry(0, QDir::cleanDirPath(e->path+"/.."), e, true);
500 }
501 }
502 else { // File
503 // we always watch the directory (DNOTIFY can't watch files alone)
504 // this notifies us about changes of files therein
505 addEntry(0, QFileInfo(e->path).dirPath(true), e, true);
506 }
507
508 return true;
509}
510#endif
511
512
513bool KDirWatchPrivate::useStat(Entry* e)
514{
515//US we have no KIO::probably_slow_mounted. So disable this part
516//US if (KIO::probably_slow_mounted(e->path))
517//US useFreq(e, m_nfsPollInterval);
518//US else
519 useFreq(e, m_PollInterval);
520
521 if (e->m_mode != StatMode) {
522 e->m_mode = StatMode;
523 statEntries++;
524
525 if ( statEntries == 1 ) {
526 // if this was first STAT entry (=timer was stopped)
527 timer->start(freq); // then start the timer
528 kdDebug(7001) << " Started Polling Timer, freq " << freq << endl;
529 }
530 }
531
532 kdDebug(7001) << " Setup Stat (freq " << e->freq
533 << ") for " << e->path << endl;
534
535 return true;
536}
537
538
539/* If <instance> !=0, this KDirWatch instance wants to watch at <_path>,
540 * providing in <isDir> the type of the entry to be watched.
541 * Sometimes, entries are dependant on each other: if <sub_entry> !=0,
542 * this entry needs another entry to watch himself (when notExistent).
543 */
544void KDirWatchPrivate::addEntry(KDirWatch* instance, const QString& _path,
545 Entry* sub_entry, bool isDir)
546{
547 QString path = _path;
548 if (path.startsWith("/dev/") || (path == "/dev"))
549 return; // Don't even go there.
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 {
557 if (sub_entry) {
558 (*it).m_entries.append(sub_entry);
559 kdDebug(7001) << "Added already watched Entry " << path
560 << " (for " << sub_entry->path << ")" << endl;
561#ifdef HAVE_DNOTIFY
562 Entry* e = &(*it);
563 if( e->dn_fd > 0 ) {
564 int mask = DN_DELETE|DN_CREATE|DN_RENAME|DN_MULTISHOT;
565 // if dependant is a file watch, we check for MODIFY & ATTRIB too
566 for(Entry* dep=e->m_entries.first();dep;dep=e->m_entries.next())
567 if (!dep->isDir) { mask |= DN_MODIFY|DN_ATTRIB; break; }
568 if( fcntl(e->dn_fd, F_NOTIFY, mask) < 0) { // shouldn't happen
569 ::close(e->dn_fd);
570 e->m_mode = UnknownMode;
571 fd_Entry.remove(e->dn_fd);
572 e->dn_fd = 0;
573 useStat( e );
574 }
575 }
576#endif
577 }
578 else {
579 (*it).addClient(instance);
580 kdDebug(7001) << "Added already watched Entry " << path
581 << " (now " << (*it).clients() << " clients)"
582 << QString(" [%1]").arg(instance->name()) << endl;
583 }
584 return;
585 }
586
587 // we have a new path to watch
588
589 struct stat stat_buf;
590 bool exists = (stat(QFile::encodeName(path), &stat_buf) == 0);
591
592 Entry newEntry;
593 m_mapEntries.insert( path, newEntry );
594 // the insert does a copy, so we have to use <e> now
595 Entry* e = &(m_mapEntries[path]);
596
597 if (exists) {
598 e->isDir = S_ISDIR(stat_buf.st_mode);
599
600 if (e->isDir && !isDir)
601 qWarning("KDirWatch: %s is a directory. Use addDir!", path.ascii());
602 else if (!e->isDir && isDir)
603 qWarning("KDirWatch: %s is a file. Use addFile!", path.ascii());
604
605 e->m_ctime = stat_buf.st_ctime;
606 e->m_status = Normal;
607 e->m_nlink = stat_buf.st_nlink;
608 }
609 else {
610 e->isDir = isDir;
611 e->m_ctime = invalid_ctime;
612 e->m_status = NonExistent;
613 e->m_nlink = 0;
614 }
615
616 e->path = path;
617 if (sub_entry)
618 e->m_entries.append(sub_entry);
619 else
620 e->addClient(instance);
621
622 kdDebug(7001) << "Added " << (e->isDir ? "Dir ":"File ") << path
623 << (e->m_status == NonExistent ? " NotExisting" : "")
624 << (sub_entry ? QString(" for %1").arg(sub_entry->path) : QString(""))
625 << (instance ? QString(" [%1]").arg(instance->name()) : QString(""))
626 << endl;
627
628
629 // now setup the notification method
630 e->m_mode = UnknownMode;
631 e->msecLeft = 0;
632
633#if defined(HAVE_FAM)
634 if (useFAM(e)) return;
635#endif
636
637#ifdef HAVE_DNOTIFY
638 if (useDNotify(e)) return;
639#endif
640
641 useStat(e);
642}
643
644
645void KDirWatchPrivate::removeEntry( KDirWatch* instance,
646 const QString& _path, Entry* sub_entry )
647{
648 Entry* e = entry(_path);
649 if (!e) {
650 kdWarning(7001) << "KDirWatch::removeDir can't handle '" << _path << "'" << endl;
651 return;
652 }
653
654 if (sub_entry)
655 e->m_entries.removeRef(sub_entry);
656 else
657 e->removeClient(instance);
658
659 if (e->m_clients.count() || e->m_entries.count())
660 return;
661
662 if (delayRemove) {
663 // removeList is allowed to contain any entry at most once
664 if (removeList.findRef(e)==-1)
665 removeList.append(e);
666 // now e->isValid() is false
667 return;
668 }
669
670#ifdef HAVE_FAM
671 if (e->m_mode == FAMMode) {
672 if ( e->m_status == Normal) {
673 FAMCancelMonitor(&fc, &(e->fr) );
674 kdDebug(7001) << "Cancelled FAM (Req "
675 << FAMREQUEST_GETREQNUM(&(e->fr))
676 << ") for " << e->path << endl;
677 }
678 else {
679 if (e->isDir)
680 removeEntry(0, QDir::cleanDirPath(e->path+"/.."), e);
681 else
682 removeEntry(0, QFileInfo(e->path).dirPath(true), e);
683 }
684 }
685#endif
686
687#ifdef HAVE_DNOTIFY
688 if (e->m_mode == DNotifyMode) {
689 if (!e->isDir) {
690 removeEntry(0, QFileInfo(e->path).dirPath(true), e);
691 }
692 else { // isDir
693 // must close the FD.
694 if ( e->m_status == Normal) {
695 if (e->dn_fd) {
696 ::close(e->dn_fd);
697 fd_Entry.remove(e->dn_fd);
698
699 kdDebug(7001) << "Cancelled DNotify (fd " << e->dn_fd
700 << ") for " << e->path << endl;
701 e->dn_fd = 0;
702
703 }
704 }
705 else {
706 removeEntry(0, QDir::cleanDirPath(e->path+"/.."), e);
707 }
708 }
709 }
710#endif
711
712 if (e->m_mode == StatMode) {
713 statEntries--;
714 if ( statEntries == 0 ) {
715 timer->stop(); // stop timer if lists are empty
716 kdDebug(7001) << " Stopped Polling Timer" << endl;
717 }
718 }
719
720 kdDebug(7001) << "Removed " << (e->isDir ? "Dir ":"File ") << e->path
721 << (sub_entry ? QString(" for %1").arg(sub_entry->path) : QString(""))
722 << (instance ? QString(" [%1]").arg(instance->name()) : QString(""))
723 << endl;
724 m_mapEntries.remove( e->path ); // <e> not valid any more
725}
726
727
728/* Called from KDirWatch destructor:
729 * remove <instance> as client from all entries
730 */
731void KDirWatchPrivate::removeEntries( KDirWatch* instance )
732{
733 QPtrList<Entry> list;
734 int minfreq = 3600000;
735
736 // put all entries where instance is a client in list
737 EntryMap::Iterator it = m_mapEntries.begin();
738 for( ; it != m_mapEntries.end(); ++it ) {
739 Client* c = (*it).m_clients.first();
740 for(;c;c=(*it).m_clients.next())
741 if (c->instance == instance) break;
742 if (c) {
743 c->count = 1; // forces deletion of instance as client
744 list.append(&(*it));
745 }
746 else if ( (*it).m_mode == StatMode && (*it).freq < minfreq )
747 minfreq = (*it).freq;
748 }
749
750 for(Entry* e=list.first();e;e=list.next())
751 removeEntry(instance, e->path, 0);
752
753 if (minfreq > freq) {
754 // we can decrease the global polling frequency
755 freq = minfreq;
756 if (timer->isActive()) timer->changeInterval(freq);
757 kdDebug(7001) << "Poll Freq now " << freq << " msec" << endl;
758 }
759}
760
761// instance ==0: stop scanning for all instances
762bool KDirWatchPrivate::stopEntryScan( KDirWatch* instance, Entry* e)
763{
764 int stillWatching = 0;
765 Client* c = e->m_clients.first();
766 for(;c;c=e->m_clients.next()) {
767 if (!instance || instance == c->instance)
768 c->watchingStopped = true;
769 else if (!c->watchingStopped)
770 stillWatching += c->count;
771 }
772
773 kdDebug(7001) << instance->name() << " stopped scanning " << e->path
774 << " (now " << stillWatching << " watchers)" << endl;
775
776 if (stillWatching == 0) {
777 // if nobody is interested, we don't watch
778 e->m_ctime = invalid_ctime; // invalid
779 // e->m_status = Normal;
780 }
781 return true;
782}
783
784// instance ==0: start scanning for all instances
785bool KDirWatchPrivate::restartEntryScan( KDirWatch* instance, Entry* e,
786 bool notify)
787{
788 int wasWatching = 0, newWatching = 0;
789 Client* c = e->m_clients.first();
790 for(;c;c=e->m_clients.next()) {
791 if (!c->watchingStopped)
792 wasWatching += c->count;
793 else if (!instance || instance == c->instance) {
794 c->watchingStopped = false;
795 newWatching += c->count;
796 }
797 }
798 if (newWatching == 0)
799 return false;
800
801 kdDebug(7001) << instance->name() << " restarted scanning " << e->path
802 << " (now " << wasWatching+newWatching << " watchers)" << endl;
803
804 // restart watching and emit pending events
805
806 int ev = NoChange;
807 if (wasWatching == 0) {
808 if (!notify) {
809 struct stat stat_buf;
810 bool exists = (stat(QFile::encodeName(e->path), &stat_buf) == 0);
811 if (exists) {
812 e->m_ctime = stat_buf.st_ctime;
813 e->m_status = Normal;
814 e->m_nlink = stat_buf.st_nlink;
815 }
816 else {
817 e->m_ctime = invalid_ctime;
818 e->m_status = NonExistent;
819 e->m_nlink = 0;
820 }
821 }
822 e->msecLeft = 0;
823 ev = scanEntry(e);
824 }
825 emitEvent(e,ev);
826
827 return true;
828}
829
830// instance ==0: stop scanning for all instances
831void KDirWatchPrivate::stopScan(KDirWatch* instance)
832{
833 EntryMap::Iterator it = m_mapEntries.begin();
834 for( ; it != m_mapEntries.end(); ++it )
835 stopEntryScan(instance, &(*it));
836}
837
838
839void KDirWatchPrivate::startScan(KDirWatch* instance,
840 bool notify, bool skippedToo )
841{
842 if (!notify)
843 resetList(instance,skippedToo);
844
845 EntryMap::Iterator it = m_mapEntries.begin();
846 for( ; it != m_mapEntries.end(); ++it )
847 restartEntryScan(instance, &(*it), notify);
848
849 // timer should still be running when in polling mode
850}
851
852
853// clear all pending events, also from stopped
854void KDirWatchPrivate::resetList( KDirWatch* /*instance*/,
855 bool skippedToo )
856{
857 EntryMap::Iterator it = m_mapEntries.begin();
858 for( ; it != m_mapEntries.end(); ++it ) {
859
860 Client* c = (*it).m_clients.first();
861 for(;c;c=(*it).m_clients.next())
862 if (!c->watchingStopped || skippedToo)
863 c->pending = NoChange;
864 }
865}
866
867// Return event happened on <e>
868//
869int KDirWatchPrivate::scanEntry(Entry* e)
870{
871#ifdef HAVE_FAM
872 // we do not stat entries using FAM
873 if (e->m_mode == FAMMode) return NoChange;
874#endif
875
876 // Shouldn't happen: Ignore "unknown" notification method
877 if (e->m_mode == UnknownMode) return NoChange;
878
879#ifdef HAVE_DNOTIFY
880 if (e->m_mode == DNotifyMode) {
881 // we know nothing has changed, no need to stat
882 if(!e->dn_dirty) return NoChange;
883 e->dn_dirty = false;
884 }
885#endif
886
887 if (e->m_mode == StatMode) {
888 // only scan if timeout on entry timer happens;
889 // e.g. when using 500msec global timer, a entry
890 // with freq=5000 is only watched every 10th time
891
892 e->msecLeft -= freq;
893 if (e->msecLeft>0) return NoChange;
894 e->msecLeft += e->freq;
895 }
896
897 struct stat stat_buf;
898 bool exists = (stat(QFile::encodeName(e->path), &stat_buf) == 0);
899 if (exists) {
900
901 if (e->m_status == NonExistent) {
902 e->m_ctime = stat_buf.st_ctime;
903 e->m_status = Normal;
904 e->m_nlink = stat_buf.st_nlink;
905 return Created;
906 }
907
908 if ( (e->m_ctime != invalid_ctime) &&
909 ((stat_buf.st_ctime != e->m_ctime) ||
910 // (stat_buf.st_nlink != (nlink_t) e->m_nlink)) ) {
911 (stat_buf.st_nlink != e->m_nlink)) ) {
912 e->m_ctime = stat_buf.st_ctime;
913 e->m_nlink = stat_buf.st_nlink;
914 return Changed;
915 }
916
917 return NoChange;
918 }
919
920 // dir/file doesn't exist
921
922 if (e->m_ctime == invalid_ctime)
923 return NoChange;
924
925 e->m_ctime = invalid_ctime;
926 e->m_nlink = 0;
927 e->m_status = NonExistent;
928
929 return Deleted;
930}
931
932/* Notify all interested KDirWatch instances about a given event on an entry
933 * and stored pending events. When watching is stopped, the event is
934 * added to the pending events.
935 */
936void KDirWatchPrivate::emitEvent(Entry* e, int event, const QString &fileName)
937{
938 QString path = e->path;
939 if (!fileName.isEmpty()) {
940 if (fileName[0] == '/')
941 path = fileName;
942 else
943 path += "/" + fileName;
944 }
945
946 Client* c = e->m_clients.first();
947 for(;c;c=e->m_clients.next()) {
948 if (c->instance==0 || c->count==0) continue;
949
950 if (c->watchingStopped) {
951 // add event to pending...
952 if (event == Changed)
953 c->pending |= event;
954 else if (event == Created || event == Deleted)
955 c->pending = event;
956 continue;
957 }
958 // not stopped
959 if (event == NoChange || event == Changed)
960 event |= c->pending;
961 c->pending = NoChange;
962 if (event == NoChange) continue;
963
964 if (event & Deleted) {
965 c->instance->setDeleted(path);
966 // emit only Deleted event...
967 continue;
968 }
969
970 if (event & Created) {
971 c->instance->setCreated(path);
972 // possible emit Change event after creation
973 }
974
975 if (event & Changed)
976 c->instance->setDirty(path);
977 }
978}
979
980// Remove entries which were marked to be removed
981void KDirWatchPrivate::slotRemoveDelayed()
982{
983 Entry* e;
984 delayRemove = false;
985 for(e=removeList.first();e;e=removeList.next())
986 removeEntry(0, e->path, 0);
987 removeList.clear();
988}
989
990/* Scan all entries to be watched for changes. This is done regularly
991 * when polling and once after a DNOTIFY signal. This is NOT used by FAM.
992 */
993void KDirWatchPrivate::slotRescan()
994{
995 EntryMap::Iterator it;
996
997 // People can do very long things in the slot connected to dirty(),
998 // like showing a message box. We don't want to keep polling during
999 // that time, otherwise the value of 'delayRemove' will be reset.
1000 bool timerRunning = timer->isActive();
1001 if ( timerRunning )
1002 timer->stop();
1003
1004 // We delay deletions of entries this way.
1005 // removeDir(), when called in slotDirty(), can cause a crash otherwise
1006 delayRemove = true;
1007
1008#ifdef HAVE_DNOTIFY
1009 QPtrList<Entry> dList, cList;
1010
1011 // for DNotify method,
1012 if (rescan_all)
1013 {
1014 // mark all as dirty
1015 it = m_mapEntries.begin();
1016 for( ; it != m_mapEntries.end(); ++it )
1017 (*it).dn_dirty = true;
1018 rescan_all = false;
1019 }
1020 else
1021 {
1022 // progate dirty flag to dependant entries (e.g. file watches)
1023 it = m_mapEntries.begin();
1024 for( ; it != m_mapEntries.end(); ++it )
1025 if ( ((*it).m_mode == DNotifyMode) && (*it).dn_dirty )
1026 (*it).propagate_dirty();
1027 }
1028
1029#endif
1030
1031 it = m_mapEntries.begin();
1032 for( ; it != m_mapEntries.end(); ++it ) {
1033 // we don't check invalid entries (i.e. remove delayed)
1034 if (!(*it).isValid()) continue;
1035
1036 int ev = scanEntry( &(*it) );
1037
1038#ifdef HAVE_DNOTIFY
1039 if ((*it).m_mode == DNotifyMode) {
1040 if ((*it).isDir && (ev == Deleted)) {
1041 dList.append( &(*it) );
1042
1043 // must close the FD.
1044 if ((*it).dn_fd) {
1045 ::close((*it).dn_fd);
1046 fd_Entry.remove((*it).dn_fd);
1047 (*it).dn_fd = 0;
1048 }
1049 }
1050
1051 else if ((*it).isDir && (ev == Created)) {
1052 // For created, but yet without DNOTIFYing ...
1053 if ( (*it).dn_fd == 0) {
1054 cList.append( &(*it) );
1055 if (! useDNotify( &(*it) )) {
1056 // if DNotify setup fails...
1057 useStat( &(*it) );
1058 }
1059 }
1060 }
1061 }
1062#endif
1063
1064 if ( ev != NoChange )
1065 emitEvent( &(*it), ev);
1066 }
1067
1068
1069#ifdef HAVE_DNOTIFY
1070 // Scan parent of deleted directories for new creation
1071 Entry* e;
1072 for(e=dList.first();e;e=dList.next())
1073 addEntry(0, QDir::cleanDirPath( e->path+"/.."), e, true);
1074
1075 // Remove watch of parent of new created directories
1076 for(e=cList.first();e;e=cList.next())
1077 removeEntry(0, QDir::cleanDirPath( e->path+"/.."), e);
1078#endif
1079
1080 if ( timerRunning )
1081 timer->start(freq);
1082
1083 QTimer::singleShot(0, this, SLOT(slotRemoveDelayed()));
1084}
1085
1086#ifdef HAVE_FAM
1087void KDirWatchPrivate::famEventReceived()
1088{
1089 static FAMEvent fe;
1090
1091 delayRemove = true;
1092
1093 while(use_fam && FAMPending(&fc)) {
1094 if (FAMNextEvent(&fc, &fe) == -1) {
1095 kdWarning(7001) << "FAM connection problem, switching to polling."
1096 << endl;
1097 use_fam = false;
1098 delete sn; sn = 0;
1099
1100 // Replace all FAMMode entries with DNotify/Stat
1101 EntryMap::Iterator it;
1102 it = m_mapEntries.begin();
1103 for( ; it != m_mapEntries.end(); ++it )
1104 if ((*it).m_mode == FAMMode && (*it).m_clients.count()>0) {
1105#ifdef HAVE_DNOTIFY
1106 if (useDNotify( &(*it) )) continue;
1107#endif
1108 useStat( &(*it) );
1109 }
1110 }
1111 else
1112 checkFAMEvent(&fe);
1113 }
1114
1115 QTimer::singleShot(0, this, SLOT(slotRemoveDelayed()));
1116}
1117
1118void KDirWatchPrivate::checkFAMEvent(FAMEvent* fe)
1119{
1120 // Don't be too verbose ;-)
1121 if ((fe->code == FAMExists) ||
1122 (fe->code == FAMEndExist) ||
1123 (fe->code == FAMAcknowledge)) return;
1124
1125 // $HOME/.X.err grows with debug output, so don't notify change
1126 if ( *(fe->filename) == '.') {
1127 if (strncmp(fe->filename, ".X.err", 6) == 0) return;
1128 if (strncmp(fe->filename, ".xsession-errors", 16) == 0) return;
1129 }
1130
1131 Entry* e = 0;
1132 EntryMap::Iterator it = m_mapEntries.begin();
1133 for( ; it != m_mapEntries.end(); ++it )
1134 if (FAMREQUEST_GETREQNUM(&( (*it).fr )) ==
1135 FAMREQUEST_GETREQNUM(&(fe->fr)) ) {
1136 e = &(*it);
1137 break;
1138 }
1139
1140 // Entry* e = static_cast<Entry*>(fe->userdata);
1141
1142 kdDebug(7001) << "Processing FAM event ("
1143 << ((fe->code == FAMChanged) ? "FAMChanged" :
1144 (fe->code == FAMDeleted) ? "FAMDeleted" :
1145 (fe->code == FAMStartExecuting) ? "FAMStartExecuting" :
1146 (fe->code == FAMStopExecuting) ? "FAMStopExecuting" :
1147 (fe->code == FAMCreated) ? "FAMCreated" :
1148 (fe->code == FAMMoved) ? "FAMMoved" :
1149 (fe->code == FAMAcknowledge) ? "FAMAcknowledge" :
1150 (fe->code == FAMExists) ? "FAMExists" :
1151 (fe->code == FAMEndExist) ? "FAMEndExist" : "Unknown Code")
1152 << ", " << fe->filename
1153 << ", Req " << FAMREQUEST_GETREQNUM(&(fe->fr))
1154 << ")" << endl;
1155
1156 if (!e) {
1157 // this happens e.g. for FAMAcknowledge after deleting a dir...
1158 // kdDebug(7001) << "No entry for FAM event ?!" << endl;
1159 return;
1160 }
1161
1162 if (e->m_status == NonExistent) {
1163 kdDebug(7001) << "FAM event for nonExistent entry " << e->path << endl;
1164 return;
1165 }
1166
1167 if (e->isDir)
1168 switch (fe->code)
1169 {
1170 case FAMDeleted:
1171 // file absolute: watched dir
1172 if (fe->filename[0] == '/')
1173 {
1174 // a watched directory was deleted
1175
1176 e->m_status = NonExistent;
1177 FAMCancelMonitor(&fc, &(e->fr) ); // needed ?
1178 kdDebug(7001) << "Cancelled FAMReq "
1179 << FAMREQUEST_GETREQNUM(&(e->fr))
1180 << " for " << e->path << endl;
1181 // Scan parent for a new creation
1182 addEntry(0, QDir::cleanDirPath( e->path+"/.."), e, true);
1183 }
1184 emitEvent(e, Deleted, QFile::decodeName(fe->filename));
1185 break;
1186
1187 case FAMCreated: {
1188 // check for creation of a directory we have to watch
1189 Entry *sub_entry = e->m_entries.first();
1190 for(;sub_entry; sub_entry = e->m_entries.next())
1191 if (sub_entry->path == e->path + "/" + fe->filename) break;
1192 if (sub_entry && sub_entry->isDir) {
1193 QString path = e->path;
1194 removeEntry(0,e->path,sub_entry); // <e> can be invalid here!!
1195 sub_entry->m_status = Normal;
1196 if (!useFAM(sub_entry))
1197 useStat(sub_entry);
1198
1199 emitEvent(sub_entry, Created);
1200 }
1201 else emitEvent(e, Created, QFile::decodeName(fe->filename));
1202 break;
1203 }
1204
1205 case FAMChanged:
1206 emitEvent(e, Changed, QFile::decodeName(fe->filename));
1207
1208 default:
1209 break;
1210 }
1211 else switch (fe->code)
1212 {
1213 case FAMCreated: emitEvent(e, Created);
1214 break;
1215 case FAMDeleted: emitEvent(e, Deleted);
1216 break;
1217 case FAMChanged: emitEvent(e, Changed);
1218 break;
1219 default: break;
1220 }
1221}
1222#else
1223void KDirWatchPrivate::famEventReceived() {}
1224#endif
1225
1226
1227void KDirWatchPrivate::statistics()
1228{
1229 EntryMap::Iterator it;
1230
1231 kdDebug(7001) << "Entries watched:" << endl;
1232 if (m_mapEntries.count()==0) {
1233 kdDebug(7001) << " None." << endl;
1234 }
1235 else {
1236 it = m_mapEntries.begin();
1237 for( ; it != m_mapEntries.end(); ++it ) {
1238 Entry* e = &(*it);
1239 kdDebug(7001) << " " << e->path << " ("
1240 << ((e->m_status==Normal)?"":"Nonexistent ")
1241 << (e->isDir ? "Dir":"File") << ", using "
1242 << ((e->m_mode == FAMMode) ? "FAM" :
1243 (e->m_mode == DNotifyMode) ? "DNotify" :
1244 (e->m_mode == StatMode) ? "Stat" : "Unknown Method")
1245 << ")" << endl;
1246
1247 Client* c = e->m_clients.first();
1248 for(;c; c = e->m_clients.next()) {
1249 QString pending;
1250 if (c->watchingStopped) {
1251 if (c->pending & Deleted) pending += "deleted ";
1252 if (c->pending & Created) pending += "created ";
1253 if (c->pending & Changed) pending += "changed ";
1254 if (!pending.isEmpty()) pending = " (pending: " + pending + ")";
1255 pending = ", stopped" + pending;
1256 }
1257 kdDebug(7001) << " by " << c->instance->name()
1258 << " (" << c->count << " times)"
1259 << pending << endl;
1260 }
1261 if (e->m_entries.count()>0) {
1262 kdDebug(7001) << " dependent entries:" << endl;
1263 Entry* d = e->m_entries.first();
1264 for(;d; d = e->m_entries.next()) {
1265 kdDebug(7001) << " " << d->path << endl;
1266 }
1267 }
1268 }
1269 }
1270}
1271
1272
1273//
1274// Class KDirWatch
1275//
1276
1277static KStaticDeleter<KDirWatch> sd_dw;
1278KDirWatch* KDirWatch::s_pSelf = 0L;
1279
1280KDirWatch* KDirWatch::self()
1281{
1282 if ( !s_pSelf ) {
1283//US sd_dw.setObject( s_pSelf, new KDirWatch );
1284 s_pSelf = sd_dw.setObject( new KDirWatch );
1285 }
1286
1287 return s_pSelf;
1288}
1289
1290bool KDirWatch::exists()
1291{
1292 return s_pSelf != 0;
1293}
1294
1295KDirWatch::KDirWatch (QObject* parent, const char* name)
1296 : QObject(parent,name)
1297{
1298 if (!name) {
1299 static int nameCounter = 0;
1300
1301 nameCounter++;
1302 setName(QString("KDirWatch-%1").arg(nameCounter).ascii());
1303 }
1304
1305 if (!dwp_self)
1306 dwp_self = new KDirWatchPrivate;
1307 d = dwp_self;
1308 d->ref();
1309
1310 _isStopped = false;
1311}
1312
1313KDirWatch::~KDirWatch()
1314{
1315 if (d) d->removeEntries(this);
1316 if ( d->deref() )
1317 {
1318 // delete it if it's the last one
1319 delete d;
1320 dwp_self = 0L;
1321 }
1322}
1323
1324
1325// TODO: add watchFiles/recursive support
1326void KDirWatch::addDir( const QString& _path,
1327 bool watchFiles, bool recursive)
1328{
1329 if (watchFiles || recursive) {
1330 kdDebug(7001) << "addDir - recursive/watchFiles not supported in KDE 3.0"
1331 << endl;
1332 }
1333 if (d) d->addEntry(this, _path, 0, true);
1334}
1335
1336void KDirWatch::addFile( const QString& _path )
1337{
1338 if (d) d->addEntry(this, _path, 0, false);
1339}
1340
1341QDateTime KDirWatch::ctime( const QString &_path )
1342{
1343 KDirWatchPrivate::Entry* e = d->entry(_path);
1344
1345 if (!e)
1346 return QDateTime();
1347
1348 QDateTime result;
1349 result.setTime_t(e->m_ctime);
1350 return result;
1351}
1352
1353void KDirWatch::removeDir( const QString& _path )
1354{
1355 if (d) d->removeEntry(this, _path, 0);
1356}
1357
1358void KDirWatch::removeFile( const QString& _path )
1359{
1360 if (d) d->removeEntry(this, _path, 0);
1361}
1362
1363bool KDirWatch::stopDirScan( const QString& _path )
1364{
1365 if (d) {
1366 KDirWatchPrivate::Entry *e = d->entry(_path);
1367 if (e && e->isDir) return d->stopEntryScan(this, e);
1368 }
1369 return false;
1370}
1371
1372bool KDirWatch::restartDirScan( const QString& _path )
1373{
1374 if (d) {
1375 KDirWatchPrivate::Entry *e = d->entry(_path);
1376 if (e && e->isDir)
1377 // restart without notifying pending events
1378 return d->restartEntryScan(this, e, false);
1379 }
1380 return false;
1381}
1382
1383void KDirWatch::stopScan()
1384{
1385 if (d) d->stopScan(this);
1386 _isStopped = true;
1387}
1388
1389void KDirWatch::startScan( bool notify, bool skippedToo )
1390{
1391 _isStopped = false;
1392 if (d) d->startScan(this, notify, skippedToo);
1393}
1394
1395
1396bool KDirWatch::contains( const QString& _path ) const
1397{
1398 KDirWatchPrivate::Entry* e = d->entry(_path);
1399 if (!e)
1400 return false;
1401
1402 KDirWatchPrivate::Client* c = e->m_clients.first();
1403 for(;c;c=e->m_clients.next())
1404 if (c->instance == this) return true;
1405
1406 return false;
1407}
1408
1409void KDirWatch::statistics()
1410{
1411 if (!dwp_self) {
1412 kdDebug(7001) << "KDirWatch not used" << endl;
1413 return;
1414 }
1415 dwp_self->statistics();
1416}
1417
1418
1419void KDirWatch::setCreated( const QString & _file )
1420{
1421 kdDebug(7001) << name() << " emitting created " << _file << endl;
1422 emit created( _file );
1423}
1424
1425void KDirWatch::setDirty( const QString & _file )
1426{
1427 kdDebug(7001) << name() << " emitting dirty " << _file << endl;
1428 emit dirty( _file );
1429}
1430
1431void KDirWatch::setDeleted( const QString & _file )
1432{
1433 kdDebug(7001) << name() << " emitting deleted " << _file << endl;
1434 emit deleted( _file );
1435}
1436
1437//US #include "kdirwatch.moc"
1438//US #include "kdirwatch_p.moc"
1439
1440//sven
1441
1442// vim: sw=2 ts=8 et