kdirwatch.cpp

00001 // -*- c-basic-offset: 2 -*-
00002 /* This file is part of the KDE libraries
00003    Copyright (C) 1998 Sven Radej <sven@lisa.exp.univie.ac.at>
00004 
00005    This library is free software; you can redistribute it and/or
00006    modify it under the terms of the GNU Library General Public
00007    License version 2 as published by the Free Software Foundation.
00008 
00009    This library is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY; without even the implied warranty of
00011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012    Library General Public License for more details.
00013 
00014    You should have received a copy of the GNU Library General Public License
00015    along with this library; see the file COPYING.LIB.  If not, write to
00016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00017    Boston, MA 02110-1301, USA.
00018 */
00019 
00020 
00021 // CHANGES:
00022 // Oct 4,  2005 - Inotify support (Dirk Mueller)
00023 // Februar 2002 - Add file watching and remote mount check for STAT
00024 // Mar 30, 2001 - Native support for Linux dir change notification.
00025 // Jan 28, 2000 - Usage of FAM service on IRIX (Josef.Weidendorfer@in.tum.de)
00026 // May 24. 1998 - List of times introduced, and some bugs are fixed. (sven)
00027 // May 23. 1998 - Removed static pointer - you can have more instances.
00028 // It was Needed for KRegistry. KDirWatch now emits signals and doesn't
00029 // call (or need) KFM. No more URL's - just plain paths. (sven)
00030 // Mar 29. 1998 - added docs, stop/restart for particular Dirs and
00031 // deep copies for list of dirs. (sven)
00032 // Mar 28. 1998 - Created.  (sven)
00033 
00034 
00035 #include <config.h>
00036 #include <errno.h>
00037 
00038 #ifdef HAVE_DNOTIFY
00039 #include <unistd.h>
00040 #include <time.h>
00041 #include <fcntl.h>
00042 #include <signal.h>
00043 #include <errno.h>
00044 #endif
00045 
00046 
00047 #include <sys/stat.h>
00048 #include <assert.h>
00049 #include <qdir.h>
00050 #include <qfile.h>
00051 #include <qintdict.h>
00052 #include <qptrlist.h>
00053 #include <qsocketnotifier.h>
00054 #include <qstringlist.h>
00055 #include <qtimer.h>
00056 
00057 #include <kapplication.h>
00058 #include <kdebug.h>
00059 #include <kconfig.h>
00060 #include <kglobal.h>
00061 #include <kstaticdeleter.h>
00062 #include <kde_file.h>
00063 
00064 // debug
00065 #include <sys/ioctl.h>
00066 
00067 #ifdef HAVE_INOTIFY
00068 #include <unistd.h>
00069 #include <fcntl.h>
00070 #include <sys/syscall.h>
00071 #include <linux/types.h>
00072 // Linux kernel headers are documented to not compile
00073 #define _S390_BITOPS_H
00074 #include <linux/inotify.h>
00075 
00076 static inline int inotify_init (void)
00077 {
00078   return syscall (__NR_inotify_init);
00079 }
00080 
00081 static inline int inotify_add_watch (int fd, const char *name, __u32 mask)
00082 {
00083   return syscall (__NR_inotify_add_watch, fd, name, mask);
00084 }
00085 
00086 static inline int inotify_rm_watch (int fd, __u32 wd)
00087 {
00088   return syscall (__NR_inotify_rm_watch, fd, wd);
00089 }
00090 
00091 #ifndef  IN_ONLYDIR
00092 #define  IN_ONLYDIR 0x01000000 
00093 #endif
00094 
00095 #ifndef IN_DONT_FOLLOW
00096 #define IN_DONT_FOLLOW 0x02000000
00097 #endif
00098 
00099 #ifndef IN_MOVE_SELF
00100 #define IN_MOVE_SELF 0x00000800
00101 #endif
00102 
00103 #endif
00104 
00105 #include <sys/utsname.h>
00106 
00107 #include "kdirwatch.h"
00108 #include "kdirwatch_p.h"
00109 #include "global.h" //  KIO::probably_slow_mounted
00110 
00111 #define NO_NOTIFY (time_t) 0
00112 
00113 static KDirWatchPrivate* dwp_self = 0;
00114 
00115 #ifdef HAVE_DNOTIFY
00116 
00117 static int dnotify_signal = 0;
00118 
00119 /* DNOTIFY signal handler
00120  *
00121  * As this is called asynchronously, only a flag is set and
00122  * a rescan is requested.
00123  * This is done by writing into a pipe to trigger a QSocketNotifier
00124  * watching on this pipe: a timer is started and after a timeout,
00125  * the rescan is done.
00126  */
00127 void KDirWatchPrivate::dnotify_handler(int, siginfo_t *si, void *)
00128 {
00129   if (!dwp_self) return;
00130 
00131   // write might change errno, we have to save it and restore it
00132   // (Richard Stevens, Advanced programming in the Unix Environment)
00133   int saved_errno = errno;
00134 
00135   Entry* e = dwp_self->fd_Entry.find(si->si_fd);
00136 
00137 //  kdDebug(7001) << "DNOTIFY Handler: fd " << si->si_fd << " path "
00138 //      << QString(e ? e->path:"unknown") << endl;
00139 
00140   if(e && e->dn_fd == si->si_fd)
00141     e->dirty = true;
00142 
00143   char c = 0;
00144   write(dwp_self->mPipe[1], &c, 1);
00145   errno = saved_errno;
00146 }
00147 
00148 static struct sigaction old_sigio_act;
00149 /* DNOTIFY SIGIO signal handler
00150  *
00151  * When the kernel queue for the dnotify_signal overflows, a SIGIO is send.
00152  */
00153 void KDirWatchPrivate::dnotify_sigio_handler(int sig, siginfo_t *si, void *p)
00154 {
00155   if (dwp_self)
00156   {
00157     // write might change errno, we have to save it and restore it
00158     // (Richard Stevens, Advanced programming in the Unix Environment)
00159     int saved_errno = errno;
00160 
00161     dwp_self->rescan_all = true;
00162     char c = 0;
00163     write(dwp_self->mPipe[1], &c, 1);
00164 
00165     errno = saved_errno;
00166   }
00167 
00168   // Call previous signal handler
00169   if (old_sigio_act.sa_flags & SA_SIGINFO)
00170   {
00171     if (old_sigio_act.sa_sigaction)
00172       (*old_sigio_act.sa_sigaction)(sig, si, p);
00173   }
00174   else
00175   {
00176     if ((old_sigio_act.sa_handler != SIG_DFL) &&
00177         (old_sigio_act.sa_handler != SIG_IGN))
00178       (*old_sigio_act.sa_handler)(sig);
00179   }
00180 }
00181 #endif
00182 
00183 
00184 //
00185 // Class KDirWatchPrivate (singleton)
00186 //
00187 
00188 /* All entries (files/directories) to be watched in the
00189  * application (coming from multiple KDirWatch instances)
00190  * are registered in a single KDirWatchPrivate instance.
00191  *
00192  * At the moment, the following methods for file watching
00193  * are supported:
00194  * - Polling: All files to be watched are polled regularly
00195  *   using stat (more precise: QFileInfo.lastModified()).
00196  *   The polling frequency is determined from global kconfig
00197  *   settings, defaulting to 500 ms for local directories
00198  *   and 5000 ms for remote mounts
00199  * - FAM (File Alternation Monitor): first used on IRIX, SGI
00200  *   has ported this method to LINUX. It uses a kernel part
00201  *   (IMON, sending change events to /dev/imon) and a user
00202  *   level damon (fam), to which applications connect for
00203  *   notification of file changes. For NFS, the fam damon
00204  *   on the NFS server machine is used; if IMON is not built
00205  *   into the kernel, fam uses polling for local files.
00206  * - DNOTIFY: In late LINUX 2.3.x, directory notification was
00207  *   introduced. By opening a directory, you can request for
00208  *   UNIX signals to be sent to the process when a directory
00209  *   is changed.
00210  * - INOTIFY: In LINUX 2.6.13, inode change notification was
00211  *   introduced. You're now able to watch arbitrary inode's
00212  *   for changes, and even get notification when they're
00213  *   unmounted.
00214  */
00215 
00216 KDirWatchPrivate::KDirWatchPrivate()
00217   : rescan_timer(0, "KDirWatchPrivate::rescan_timer")
00218 {
00219   timer = new QTimer(this, "KDirWatchPrivate::timer");
00220   connect (timer, SIGNAL(timeout()), this, SLOT(slotRescan()));
00221   freq = 3600000; // 1 hour as upper bound
00222   statEntries = 0;
00223   delayRemove = false;
00224   m_ref = 0;
00225 
00226   KConfigGroup config(KGlobal::config(), QCString("DirWatch"));
00227   m_nfsPollInterval = config.readNumEntry("NFSPollInterval", 5000);
00228   m_PollInterval = config.readNumEntry("PollInterval", 500);
00229 
00230   QString available("Stat");
00231 
00232   // used for FAM and DNOTIFY
00233   rescan_all = false;
00234   connect(&rescan_timer, SIGNAL(timeout()), this, SLOT(slotRescan()));
00235 
00236 #ifdef HAVE_FAM
00237   // It's possible that FAM server can't be started
00238   if (FAMOpen(&fc) ==0) {
00239     available += ", FAM";
00240     use_fam=true;
00241     sn = new QSocketNotifier( FAMCONNECTION_GETFD(&fc),
00242                   QSocketNotifier::Read, this);
00243     connect( sn, SIGNAL(activated(int)),
00244          this, SLOT(famEventReceived()) );
00245   }
00246   else {
00247     kdDebug(7001) << "Can't use FAM (fam daemon not running?)" << endl;
00248     use_fam=false;
00249   }
00250 #endif
00251 
00252 #ifdef HAVE_INOTIFY
00253   supports_inotify = true;
00254 
00255   m_inotify_fd = inotify_init();
00256 
00257   if ( m_inotify_fd <= 0 ) {
00258     kdDebug(7001) << "Can't use Inotify, kernel doesn't support it" << endl;
00259     supports_inotify = false;
00260   }
00261 
00262   {
00263     struct utsname uts;
00264     int major, minor, patch;
00265     if (uname(&uts) < 0)
00266       supports_inotify = false; // *shrug*
00267     else if (sscanf(uts.release, "%d.%d.%d", &major, &minor, &patch) != 3)
00268       supports_inotify = false; // *shrug*
00269     else if( major * 1000000 + minor * 1000 + patch < 2006014 ) { // <2.6.14
00270       kdDebug(7001) << "Can't use INotify, Linux kernel too old" << endl;
00271       supports_inotify = false;
00272     }
00273   }
00274 
00275   if ( supports_inotify ) {
00276     available += ", Inotify";
00277     fcntl(m_inotify_fd, F_SETFD, FD_CLOEXEC);
00278 
00279     mSn = new QSocketNotifier( m_inotify_fd, QSocketNotifier::Read, this );
00280     connect( mSn, SIGNAL(activated( int )), this, SLOT( slotActivated() ) );
00281   }
00282 #endif
00283 
00284 #ifdef HAVE_DNOTIFY
00285 
00286   // if we have inotify, disable dnotify.
00287 #ifdef HAVE_INOTIFY
00288   supports_dnotify = !supports_inotify;
00289 #else
00290   // otherwise, not guilty until proven guilty.
00291   supports_dnotify = true;
00292 #endif
00293 
00294   struct utsname uts;
00295   int major, minor, patch;
00296   if (uname(&uts) < 0)
00297     supports_dnotify = false; // *shrug*
00298   else if (sscanf(uts.release, "%d.%d.%d", &major, &minor, &patch) != 3)
00299     supports_dnotify = false; // *shrug*
00300   else if( major * 1000000 + minor * 1000 + patch < 2004019 ) { // <2.4.19
00301     kdDebug(7001) << "Can't use DNotify, Linux kernel too old" << endl;
00302     supports_dnotify = false;
00303   }
00304 
00305   if( supports_dnotify ) {
00306     available += ", DNotify";
00307 
00308     pipe(mPipe);
00309     fcntl(mPipe[0], F_SETFD, FD_CLOEXEC);
00310     fcntl(mPipe[1], F_SETFD, FD_CLOEXEC);
00311     fcntl(mPipe[0], F_SETFL, O_NONBLOCK | fcntl(mPipe[0], F_GETFL));
00312     fcntl(mPipe[1], F_SETFL, O_NONBLOCK | fcntl(mPipe[1], F_GETFL));
00313     mSn = new QSocketNotifier( mPipe[0], QSocketNotifier::Read, this);
00314     connect(mSn, SIGNAL(activated(int)), this, SLOT(slotActivated()));
00315     // Install the signal handler only once
00316     if ( dnotify_signal == 0 )
00317     {
00318        dnotify_signal = SIGRTMIN + 8;
00319 
00320        struct sigaction act;
00321        act.sa_sigaction = KDirWatchPrivate::dnotify_handler;
00322        sigemptyset(&act.sa_mask);
00323        act.sa_flags = SA_SIGINFO;
00324 #ifdef SA_RESTART
00325        act.sa_flags |= SA_RESTART;
00326 #endif
00327        sigaction(dnotify_signal, &act, NULL);
00328 
00329        act.sa_sigaction = KDirWatchPrivate::dnotify_sigio_handler;
00330        sigaction(SIGIO, &act, &old_sigio_act);
00331     }
00332   }
00333   else
00334   {
00335     mPipe[0] = -1;
00336     mPipe[1] = -1;
00337   }
00338 #endif
00339 
00340   kdDebug(7001) << "Available methods: " << available << endl;
00341 }
00342 
00343 /* This is called on app exit (KStaticDeleter) */
00344 KDirWatchPrivate::~KDirWatchPrivate()
00345 {
00346   timer->stop();
00347 
00348   /* remove all entries being watched */
00349   removeEntries(0);
00350 
00351 #ifdef HAVE_FAM
00352   if (use_fam) {
00353     FAMClose(&fc);
00354     kdDebug(7001) << "KDirWatch deleted (FAM closed)" << endl;
00355   }
00356 #endif
00357 #ifdef HAVE_INOTIFY
00358   if ( supports_inotify )
00359     ::close( m_inotify_fd );
00360 #endif
00361 #ifdef HAVE_DNOTIFY
00362   close(mPipe[0]);
00363   close(mPipe[1]);
00364 #endif
00365 }
00366 
00367 #include <stdlib.h>
00368 
00369 void KDirWatchPrivate::slotActivated()
00370 {
00371 #ifdef HAVE_DNOTIFY
00372   if ( supports_dnotify )
00373   {
00374     char dummy_buf[4096];
00375     read(mPipe[0], &dummy_buf, 4096);
00376 
00377     if (!rescan_timer.isActive())
00378       rescan_timer.start(m_PollInterval, true /* singleshot */);
00379 
00380     return;
00381   }
00382 #endif
00383 
00384 #ifdef HAVE_INOTIFY
00385   if ( !supports_inotify )
00386     return;
00387 
00388   int pending = -1;
00389   int offset = 0;
00390   char buf[4096];
00391   assert( m_inotify_fd > -1 );
00392   ioctl( m_inotify_fd, FIONREAD, &pending );
00393 
00394   while ( pending > 0 ) {
00395 
00396     if ( pending > (int)sizeof( buf ) )
00397       pending = sizeof( buf );
00398 
00399     pending = read( m_inotify_fd, buf, pending);
00400 
00401     while ( pending > 0 ) {
00402       struct inotify_event *event = (struct inotify_event *) &buf[offset];
00403       pending -= sizeof( struct inotify_event ) + event->len;
00404       offset += sizeof( struct inotify_event ) + event->len;
00405 
00406       QString path;
00407       if ( event->len )
00408         path = QFile::decodeName( QCString( event->name, event->len ) );
00409 
00410       if ( path.length() && isNoisyFile( path.latin1() ) )
00411         continue;
00412 
00413       kdDebug(7001) << "ev wd: " << event->wd << " mask " << event->mask << " path: " << path << endl;
00414 
00415       // now we're in deep trouble of finding the
00416       // associated entries
00417       // for now, we suck and iterate
00418       for ( EntryMap::Iterator it = m_mapEntries.begin();
00419             it != m_mapEntries.end(); ++it ) {
00420         Entry* e = &( *it );
00421         if ( e->wd == event->wd ) {
00422           e->dirty = true;
00423 
00424           if ( 1 || e->isDir) {
00425             if( event->mask & IN_DELETE_SELF) {
00426               kdDebug(7001) << "-->got deleteself signal for " << e->path << endl;
00427               e->m_status = NonExistent;
00428               if (e->isDir)
00429                 addEntry(0, QDir::cleanDirPath(e->path+"/.."), e, true);
00430               else
00431                 addEntry(0, QFileInfo(e->path).dirPath(true), e, true);
00432             }
00433             if ( event->mask & IN_IGNORED ) {
00434               e->wd = 0;
00435             }
00436             if ( event->mask & (IN_CREATE|IN_MOVED_TO) ) {
00437               Entry *sub_entry = e->m_entries.first();
00438               for(;sub_entry; sub_entry = e->m_entries.next())
00439                 if (sub_entry->path == e->path + "/" + path) break;
00440 
00441               if (sub_entry /*&& sub_entry->isDir*/) {
00442                 removeEntry(0,e->path, sub_entry);
00443                 KDE_struct_stat stat_buf;
00444                 QCString tpath = QFile::encodeName(path);
00445                 KDE_stat(tpath, &stat_buf);
00446 
00447                 //sub_entry->isDir = S_ISDIR(stat_buf.st_mode);
00448                 //sub_entry->m_ctime = stat_buf.st_ctime;
00449                 //sub_entry->m_status = Normal;
00450                 //sub_entry->m_nlink = stat_buf.st_nlink;
00451 
00452                 if(!useINotify(sub_entry))
00453                   useStat(sub_entry);
00454                 sub_entry->dirty = true;
00455               }
00456             }
00457           }
00458 
00459           if (!rescan_timer.isActive())
00460             rescan_timer.start(m_PollInterval, true /* singleshot */);
00461 
00462           break; // there really should be only one matching wd
00463         }
00464       }
00465 
00466     }
00467   }
00468 #endif
00469 }
00470 
00471 /* In DNOTIFY/FAM mode, only entries which are marked dirty are scanned.
00472  * We first need to mark all yet nonexistent, but possible created
00473  * entries as dirty...
00474  */
00475 void KDirWatchPrivate::Entry::propagate_dirty()
00476 {
00477   for (QPtrListIterator<Entry> sub_entry (m_entries); 
00478        sub_entry.current(); ++sub_entry)
00479   {
00480      if (!sub_entry.current()->dirty)
00481      {
00482         sub_entry.current()->dirty = true;
00483         sub_entry.current()->propagate_dirty();
00484      }
00485   }
00486 }
00487 
00488 
00489 /* A KDirWatch instance is interested in getting events for
00490  * this file/Dir entry.
00491  */
00492 void KDirWatchPrivate::Entry::addClient(KDirWatch* instance)
00493 {
00494   Client* client = m_clients.first();
00495   for(;client; client = m_clients.next())
00496     if (client->instance == instance) break;
00497 
00498   if (client) {
00499     client->count++;
00500     return;
00501   }
00502 
00503   client = new Client;
00504   client->instance = instance;
00505   client->count = 1;
00506   client->watchingStopped = instance->isStopped();
00507   client->pending = NoChange;
00508 
00509   m_clients.append(client);
00510 }
00511 
00512 void KDirWatchPrivate::Entry::removeClient(KDirWatch* instance)
00513 {
00514   Client* client = m_clients.first();
00515   for(;client; client = m_clients.next())
00516     if (client->instance == instance) break;
00517 
00518   if (client) {
00519     client->count--;
00520     if (client->count == 0) {
00521       m_clients.removeRef(client);
00522       delete client;
00523     }
00524   }
00525 }
00526 
00527 /* get number of clients */
00528 int KDirWatchPrivate::Entry::clients()
00529 {
00530   int clients = 0;
00531   Client* client = m_clients.first();
00532   for(;client; client = m_clients.next())
00533     clients += client->count;
00534 
00535   return clients;
00536 }
00537 
00538 
00539 KDirWatchPrivate::Entry* KDirWatchPrivate::entry(const QString& _path)
00540 {
00541 // we only support absolute paths
00542   if (QDir::isRelativePath(_path)) {
00543     return 0;
00544   }
00545 
00546   QString path = _path;
00547 
00548   if ( path.length() > 1 && path.right(1) == "/" )
00549     path.truncate( path.length() - 1 );
00550 
00551   EntryMap::Iterator it = m_mapEntries.find( path );
00552   if ( it == m_mapEntries.end() )
00553     return 0;
00554   else
00555     return &(*it);
00556 }
00557 
00558 // set polling frequency for a entry and adjust global freq if needed
00559 void KDirWatchPrivate::useFreq(Entry* e, int newFreq)
00560 {
00561   e->freq = newFreq;
00562 
00563   // a reasonable frequency for the global polling timer
00564   if (e->freq < freq) {
00565     freq = e->freq;
00566     if (timer->isActive()) timer->changeInterval(freq);
00567     kdDebug(7001) << "Global Poll Freq is now " << freq << " msec" << endl;
00568   }
00569 }
00570 
00571 
00572 #ifdef HAVE_FAM
00573 // setup FAM notification, returns false if not possible
00574 bool KDirWatchPrivate::useFAM(Entry* e)
00575 {
00576   if (!use_fam) return false;
00577 
00578   // handle FAM events to avoid deadlock
00579   // (FAM sends back all files in a directory when monitoring)
00580   famEventReceived();
00581 
00582   e->m_mode = FAMMode;
00583   e->dirty = false;
00584 
00585   if (e->isDir) {
00586     if (e->m_status == NonExistent) {
00587       // If the directory does not exist we watch the parent directory
00588       addEntry(0, QDir::cleanDirPath(e->path+"/.."), e, true);
00589     }
00590     else {
00591       int res =FAMMonitorDirectory(&fc, QFile::encodeName(e->path),
00592                    &(e->fr), e);
00593       if (res<0) {
00594     e->m_mode = UnknownMode;
00595     use_fam=false;
00596     return false;
00597       }
00598       kdDebug(7001) << " Setup FAM (Req "
00599             << FAMREQUEST_GETREQNUM(&(e->fr))
00600             << ") for " << e->path << endl;
00601     }
00602   }
00603   else {
00604     if (e->m_status == NonExistent) {
00605       // If the file does not exist we watch the directory
00606       addEntry(0, QFileInfo(e->path).dirPath(true), e, true);
00607     }
00608     else {
00609       int res = FAMMonitorFile(&fc, QFile::encodeName(e->path),
00610                    &(e->fr), e);
00611       if (res<0) {
00612     e->m_mode = UnknownMode;
00613     use_fam=false;
00614     return false;
00615       }
00616 
00617       kdDebug(7001) << " Setup FAM (Req "
00618             << FAMREQUEST_GETREQNUM(&(e->fr))
00619             << ") for " << e->path << endl;
00620     }
00621   }
00622 
00623   // handle FAM events to avoid deadlock
00624   // (FAM sends back all files in a directory when monitoring)
00625   famEventReceived();
00626 
00627   return true;
00628 }
00629 #endif
00630 
00631 
00632 #ifdef HAVE_DNOTIFY
00633 // setup DNotify notification, returns false if not possible
00634 bool KDirWatchPrivate::useDNotify(Entry* e)
00635 {
00636   e->dn_fd = 0;
00637   e->dirty = false;
00638   if (!supports_dnotify) return false;
00639 
00640   e->m_mode = DNotifyMode;
00641 
00642   if (e->isDir) {
00643     if (e->m_status == Normal) {
00644       int fd = KDE_open(QFile::encodeName(e->path).data(), O_RDONLY);
00645       // Migrate fd to somewhere above 128. Some libraries have
00646       // constructs like:
00647       //    fd = socket(...)
00648       //    if (fd > ARBITRARY_LIMIT)
00649       //       return error;
00650       //
00651       // Since programs might end up using a lot of KDirWatch objects
00652       // for a rather long time the above braindamage could get
00653       // triggered.
00654       //
00655       // By moving the kdirwatch fd's to > 128, calls like socket() will keep
00656       // returning fd's < ARBITRARY_LIMIT for a bit longer.
00657       int fd2 = fcntl(fd, F_DUPFD, 128);
00658       if (fd2 >= 0)
00659       {
00660         close(fd);
00661         fd = fd2;
00662       }
00663       if (fd<0) {
00664     e->m_mode = UnknownMode;
00665     return false;
00666       }
00667 
00668       int mask = DN_DELETE|DN_CREATE|DN_RENAME|DN_MULTISHOT;
00669       // if dependant is a file watch, we check for MODIFY & ATTRIB too
00670       for(Entry* dep=e->m_entries.first();dep;dep=e->m_entries.next())
00671     if (!dep->isDir) { mask |= DN_MODIFY|DN_ATTRIB; break; }
00672 
00673       if(fcntl(fd, F_SETSIG, dnotify_signal) < 0 ||
00674      fcntl(fd, F_NOTIFY, mask) < 0) {
00675 
00676     kdDebug(7001) << "Not using Linux Directory Notifications."
00677               << endl;
00678     supports_dnotify = false;
00679     ::close(fd);
00680     e->m_mode = UnknownMode;
00681     return false;
00682       }
00683 
00684       fd_Entry.replace(fd, e);
00685       e->dn_fd = fd;
00686 
00687       kdDebug(7001) << " Setup DNotify (fd " << fd
00688             << ") for " << e->path << endl;
00689     }
00690     else { // NotExisting
00691       addEntry(0, QDir::cleanDirPath(e->path+"/.."), e, true);
00692     }
00693   }
00694   else { // File
00695     // we always watch the directory (DNOTIFY can't watch files alone)
00696     // this notifies us about changes of files therein
00697     addEntry(0, QFileInfo(e->path).dirPath(true), e, true);
00698   }
00699 
00700   return true;
00701 }
00702 #endif
00703 
00704 #ifdef HAVE_INOTIFY
00705 // setup INotify notification, returns false if not possible
00706 bool KDirWatchPrivate::useINotify( Entry* e )
00707 {
00708   e->wd = 0;
00709   e->dirty = false;
00710   if (!supports_inotify) return false;
00711 
00712   e->m_mode = INotifyMode;
00713 
00714   int mask = IN_DELETE|IN_DELETE_SELF|IN_CREATE|IN_MOVE|IN_MOVE_SELF|IN_DONT_FOLLOW;
00715   if(!e->isDir)
00716     mask |= IN_MODIFY|IN_ATTRIB;
00717   else
00718     mask |= IN_ONLYDIR;
00719 
00720   // if dependant is a file watch, we check for MODIFY & ATTRIB too
00721   for(Entry* dep=e->m_entries.first();dep;dep=e->m_entries.next()) {
00722     if (!dep->isDir) { mask |= IN_MODIFY|IN_ATTRIB; break; }
00723   }
00724 
00725   if ( ( e->wd = inotify_add_watch( m_inotify_fd,
00726         QFile::encodeName( e->path ), mask) ) > 0 )
00727     return true;
00728 
00729   if ( e->m_status == NonExistent ) {
00730     if (e->isDir) 
00731       addEntry(0, QDir::cleanDirPath(e->path+"/.."), e, true);
00732     else
00733       addEntry(0, QFileInfo(e->path).dirPath(true), e, true);
00734     return true;
00735   }
00736 
00737   return false;
00738 }
00739 #endif
00740 
00741 bool KDirWatchPrivate::useStat(Entry* e)
00742 {
00743   if ( e->path.startsWith("/media/") || (e->path == "/media")
00744        || (KIO::probably_slow_mounted(e->path)) )
00745     useFreq(e, m_nfsPollInterval);
00746   else
00747     useFreq(e, m_PollInterval);
00748 
00749   if (e->m_mode != StatMode) {
00750     e->m_mode = StatMode;
00751     statEntries++;
00752 
00753     if ( statEntries == 1 ) {
00754       // if this was first STAT entry (=timer was stopped)
00755       timer->start(freq);      // then start the timer
00756       kdDebug(7001) << " Started Polling Timer, freq " << freq << endl;
00757     }
00758   }
00759 
00760   kdDebug(7001) << " Setup Stat (freq " << e->freq
00761         << ") for " << e->path << endl;
00762 
00763   return true;
00764 }
00765 
00766 
00767 /* If <instance> !=0, this KDirWatch instance wants to watch at <_path>,
00768  * providing in <isDir> the type of the entry to be watched.
00769  * Sometimes, entries are dependant on each other: if <sub_entry> !=0,
00770  * this entry needs another entry to watch himself (when notExistent).
00771  */
00772 void KDirWatchPrivate::addEntry(KDirWatch* instance, const QString& _path,
00773                 Entry* sub_entry, bool isDir)
00774 {
00775   QString path = _path;
00776   if (path.startsWith("/dev/") || (path == "/dev"))
00777     return; // Don't even go there.
00778 
00779   if ( path.length() > 1 && path.right(1) == "/" )
00780     path.truncate( path.length() - 1 );
00781 
00782   EntryMap::Iterator it = m_mapEntries.find( path );
00783   if ( it != m_mapEntries.end() )
00784   {
00785     if (sub_entry) {
00786        (*it).m_entries.append(sub_entry);
00787        kdDebug(7001) << "Added already watched Entry " << path
00788              << " (for " << sub_entry->path << ")" << endl;
00789 
00790 #ifdef HAVE_DNOTIFY
00791      {
00792        Entry* e = &(*it);
00793        if( (e->m_mode == DNotifyMode) && (e->dn_fd > 0) ) {
00794          int mask = DN_DELETE|DN_CREATE|DN_RENAME|DN_MULTISHOT;
00795          // if dependant is a file watch, we check for MODIFY & ATTRIB too
00796          for(Entry* dep=e->m_entries.first();dep;dep=e->m_entries.next())
00797            if (!dep->isDir) { mask |= DN_MODIFY|DN_ATTRIB; break; }
00798      if( fcntl(e->dn_fd, F_NOTIFY, mask) < 0) { // shouldn't happen
00799        ::close(e->dn_fd);
00800        e->m_mode = UnknownMode;
00801        fd_Entry.remove(e->dn_fd);
00802            e->dn_fd = 0;
00803            useStat( e );
00804          }
00805        }
00806      }
00807 #endif
00808 
00809 #ifdef HAVE_INOTIFY
00810      {
00811        Entry* e = &(*it);
00812        if( (e->m_mode == INotifyMode) && (e->wd > 0) ) {
00813          int mask = IN_DELETE|IN_DELETE_SELF|IN_CREATE|IN_MOVE|IN_MOVE_SELF|IN_DONT_FOLLOW;
00814          if(!e->isDir)
00815            mask |= IN_MODIFY|IN_ATTRIB;
00816          else
00817            mask |= IN_ONLYDIR;
00818 
00819          inotify_rm_watch (m_inotify_fd, e->wd);
00820          e->wd = inotify_add_watch( m_inotify_fd, QFile::encodeName( e->path ), mask);
00821        }
00822     }
00823 #endif
00824  
00825     }
00826     else {
00827        (*it).addClient(instance);
00828        kdDebug(7001) << "Added already watched Entry " << path
00829              << " (now " <<  (*it).clients() << " clients)"
00830              << QString(" [%1]").arg(instance->name()) << endl;
00831     }
00832     return;
00833   }
00834 
00835   // we have a new path to watch
00836 
00837   KDE_struct_stat stat_buf;
00838   QCString tpath = QFile::encodeName(path);
00839   bool exists = (KDE_stat(tpath, &stat_buf) == 0);
00840 
00841   Entry newEntry;
00842   m_mapEntries.insert( path, newEntry );
00843   // the insert does a copy, so we have to use <e> now
00844   Entry* e = &(m_mapEntries[path]);
00845 
00846   if (exists) {
00847     e->isDir = S_ISDIR(stat_buf.st_mode);
00848 
00849     if (e->isDir && !isDir)
00850       kdWarning() << "KDirWatch: " << path << " is a directory. Use addDir!" << endl;
00851     else if (!e->isDir && isDir)
00852       kdWarning() << "KDirWatch: " << path << " is a file. Use addFile!" << endl;
00853 
00854     e->m_ctime = stat_buf.st_ctime;
00855     e->m_status = Normal;
00856     e->m_nlink = stat_buf.st_nlink;
00857   }
00858   else {
00859     e->isDir = isDir;
00860     e->m_ctime = invalid_ctime;
00861     e->m_status = NonExistent;
00862     e->m_nlink = 0;
00863   }
00864 
00865   e->path = path;
00866   if (sub_entry)
00867      e->m_entries.append(sub_entry);
00868   else
00869     e->addClient(instance);
00870 
00871   kdDebug(7001) << "Added " << (e->isDir ? "Dir ":"File ") << path
00872         << (e->m_status == NonExistent ? " NotExisting" : "")
00873         << (sub_entry ? QString(" for %1").arg(sub_entry->path) : QString(""))
00874         << (instance ? QString(" [%1]").arg(instance->name()) : QString(""))
00875         << endl;
00876 
00877 
00878   // now setup the notification method
00879   e->m_mode = UnknownMode;
00880   e->msecLeft = 0;
00881 
00882   if ( isNoisyFile( tpath ) )
00883     return;
00884 
00885 #ifdef HAVE_FAM
00886   if (useFAM(e)) return;
00887 #endif
00888 
00889 #ifdef HAVE_INOTIFY
00890   if (useINotify(e)) return;
00891 #endif
00892 
00893 #ifdef HAVE_DNOTIFY
00894   if (useDNotify(e)) return;
00895 #endif
00896 
00897   useStat(e);
00898 }
00899 
00900 
00901 void KDirWatchPrivate::removeEntry( KDirWatch* instance,
00902                     const QString& _path, Entry* sub_entry )
00903 {
00904   kdDebug(7001) << "KDirWatchPrivate::removeEntry for '" << _path << "' sub_entry: " << sub_entry << endl;
00905   Entry* e = entry(_path);
00906   if (!e) {
00907     kdDebug(7001) << "KDirWatchPrivate::removeEntry can't handle '" << _path << "'" << endl;
00908     return;
00909   }
00910 
00911   if (sub_entry)
00912     e->m_entries.removeRef(sub_entry);
00913   else
00914     e->removeClient(instance);
00915 
00916   if (e->m_clients.count() || e->m_entries.count()) {
00917     kdDebug(7001) << "removeEntry: unwatched " << e->path << " " << _path << endl;
00918     return;
00919   }
00920 
00921   if (delayRemove) {
00922     // removeList is allowed to contain any entry at most once
00923     if (removeList.findRef(e)==-1)
00924       removeList.append(e);
00925     // now e->isValid() is false
00926     return;
00927   }
00928 
00929 #ifdef HAVE_FAM
00930   if (e->m_mode == FAMMode) {
00931     if ( e->m_status == Normal) {
00932       FAMCancelMonitor(&fc, &(e->fr) );
00933       kdDebug(7001) << "Cancelled FAM (Req "
00934             << FAMREQUEST_GETREQNUM(&(e->fr))
00935             << ") for " << e->path << endl;
00936     }
00937     else {
00938       if (e->isDir)
00939     removeEntry(0, QDir::cleanDirPath(e->path+"/.."), e);
00940       else
00941     removeEntry(0, QFileInfo(e->path).dirPath(true), e);
00942     }
00943   }
00944 #endif
00945 
00946 #ifdef HAVE_INOTIFY
00947   kdDebug(7001) << "inotify remove " << ( e->m_mode == INotifyMode ) << " " << ( e->m_status == Normal )  << endl;
00948   if (e->m_mode == INotifyMode) {
00949     if ( e->m_status == Normal ) {
00950       (void) inotify_rm_watch( m_inotify_fd, e->wd );
00951       kdDebug(7001) << "Cancelled INotify (fd " <<
00952         m_inotify_fd << ", "  << e->wd <<
00953         ") for " << e->path << endl;
00954     }
00955     else {
00956       if (e->isDir)
00957     removeEntry(0, QDir::cleanDirPath(e->path+"/.."), e);
00958       else
00959     removeEntry(0, QFileInfo(e->path).dirPath(true), e);
00960     }
00961   }
00962 #endif
00963 
00964 #ifdef HAVE_DNOTIFY
00965   if (e->m_mode == DNotifyMode) {
00966     if (!e->isDir) {
00967       removeEntry(0, QFileInfo(e->path).dirPath(true), e);
00968     }
00969     else { // isDir
00970       // must close the FD.
00971       if ( e->m_status == Normal) {
00972     if (e->dn_fd) {
00973       ::close(e->dn_fd);
00974       fd_Entry.remove(e->dn_fd);
00975 
00976       kdDebug(7001) << "Cancelled DNotify (fd " << e->dn_fd
00977             << ") for " << e->path << endl;
00978       e->dn_fd = 0;
00979 
00980     }
00981       }
00982       else {
00983     removeEntry(0, QDir::cleanDirPath(e->path+"/.."), e);
00984       }
00985     }
00986   }
00987 #endif
00988 
00989   if (e->m_mode == StatMode) {
00990     statEntries--;
00991     if ( statEntries == 0 ) {
00992       timer->stop(); // stop timer if lists are empty
00993       kdDebug(7001) << " Stopped Polling Timer" << endl;
00994     }
00995   }
00996 
00997   kdDebug(7001) << "Removed " << (e->isDir ? "Dir ":"File ") << e->path
00998         << (sub_entry ? QString(" for %1").arg(sub_entry->path) : QString(""))
00999         << (instance ? QString(" [%1]").arg(instance->name()) : QString(""))
01000         << endl;
01001   m_mapEntries.remove( e->path ); // <e> not valid any more
01002 }
01003 
01004 
01005 /* Called from KDirWatch destructor:
01006  * remove <instance> as client from all entries
01007  */
01008 void KDirWatchPrivate::removeEntries( KDirWatch* instance )
01009 {
01010   QPtrList<Entry> list;
01011   int minfreq = 3600000;
01012 
01013   // put all entries where instance is a client in list
01014   EntryMap::Iterator it = m_mapEntries.begin();
01015   for( ; it != m_mapEntries.end(); ++it ) {
01016     Client* c = (*it).m_clients.first();
01017     for(;c;c=(*it).m_clients.next())
01018       if (c->instance == instance) break;
01019     if (c) {
01020       c->count = 1; // forces deletion of instance as client
01021       list.append(&(*it));
01022     }
01023     else if ( (*it).m_mode == StatMode && (*it).freq < minfreq )
01024       minfreq = (*it).freq;
01025   }
01026 
01027   for(Entry* e=list.first();e;e=list.next())
01028     removeEntry(instance, e->path, 0);
01029 
01030   if (minfreq > freq) {
01031     // we can decrease the global polling frequency
01032     freq = minfreq;
01033     if (timer->isActive()) timer->changeInterval(freq);
01034     kdDebug(7001) << "Poll Freq now " << freq << " msec" << endl;
01035   }
01036 }
01037 
01038 // instance ==0: stop scanning for all instances
01039 bool KDirWatchPrivate::stopEntryScan( KDirWatch* instance, Entry* e)
01040 {
01041   int stillWatching = 0;
01042   Client* c = e->m_clients.first();
01043   for(;c;c=e->m_clients.next()) {
01044     if (!instance || instance == c->instance)
01045       c->watchingStopped = true;
01046     else if (!c->watchingStopped)
01047       stillWatching += c->count;
01048   }
01049 
01050   kdDebug(7001) << instance->name() << " stopped scanning " << e->path
01051         << " (now " << stillWatching << " watchers)" << endl;
01052 
01053   if (stillWatching == 0) {
01054     // if nobody is interested, we don't watch
01055     e->m_ctime = invalid_ctime; // invalid
01056     e->m_status = NonExistent;
01057     //    e->m_status = Normal;
01058   }
01059   return true;
01060 }
01061 
01062 // instance ==0: start scanning for all instances
01063 bool KDirWatchPrivate::restartEntryScan( KDirWatch* instance, Entry* e,
01064                      bool notify)
01065 {
01066   int wasWatching = 0, newWatching = 0;
01067   Client* c = e->m_clients.first();
01068   for(;c;c=e->m_clients.next()) {
01069     if (!c->watchingStopped)
01070       wasWatching += c->count;
01071     else if (!instance || instance == c->instance) {
01072       c->watchingStopped = false;
01073       newWatching += c->count;
01074     }
01075   }
01076   if (newWatching == 0)
01077     return false;
01078 
01079   kdDebug(7001) << (instance ? instance->name() : "all") << " restarted scanning " << e->path
01080         << " (now " << wasWatching+newWatching << " watchers)" << endl;
01081 
01082   // restart watching and emit pending events
01083 
01084   int ev = NoChange;
01085   if (wasWatching == 0) {
01086     if (!notify) {
01087       KDE_struct_stat stat_buf;
01088       bool exists = (KDE_stat(QFile::encodeName(e->path), &stat_buf) == 0);
01089       if (exists) {
01090     e->m_ctime = stat_buf.st_ctime;
01091     e->m_status = Normal;
01092         e->m_nlink = stat_buf.st_nlink;
01093       }
01094       else {
01095     e->m_ctime = invalid_ctime;
01096     e->m_status = NonExistent;
01097         e->m_nlink = 0;
01098       }
01099     }
01100     e->msecLeft = 0;
01101     ev = scanEntry(e);
01102   }
01103   emitEvent(e,ev);
01104 
01105   return true;
01106 }
01107 
01108 // instance ==0: stop scanning for all instances
01109 void KDirWatchPrivate::stopScan(KDirWatch* instance)
01110 {
01111   EntryMap::Iterator it = m_mapEntries.begin();
01112   for( ; it != m_mapEntries.end(); ++it )
01113     stopEntryScan(instance, &(*it));
01114 }
01115 
01116 
01117 void KDirWatchPrivate::startScan(KDirWatch* instance,
01118                  bool notify, bool skippedToo )
01119 {
01120   if (!notify)
01121     resetList(instance,skippedToo);
01122 
01123   EntryMap::Iterator it = m_mapEntries.begin();
01124   for( ; it != m_mapEntries.end(); ++it )
01125     restartEntryScan(instance, &(*it), notify);
01126 
01127   // timer should still be running when in polling mode
01128 }
01129 
01130 
01131 // clear all pending events, also from stopped
01132 void KDirWatchPrivate::resetList( KDirWatch* /*instance*/,
01133                   bool skippedToo )
01134 {
01135   EntryMap::Iterator it = m_mapEntries.begin();
01136   for( ; it != m_mapEntries.end(); ++it ) {
01137 
01138     Client* c = (*it).m_clients.first();
01139     for(;c;c=(*it).m_clients.next())
01140       if (!c->watchingStopped || skippedToo)
01141     c->pending = NoChange;
01142   }
01143 }
01144 
01145 // Return event happened on <e>
01146 //
01147 int KDirWatchPrivate::scanEntry(Entry* e)
01148 {
01149 #ifdef HAVE_FAM
01150   if (e->m_mode == FAMMode) {
01151     // we know nothing has changed, no need to stat
01152     if(!e->dirty) return NoChange;
01153     e->dirty = false;
01154   }
01155 #endif
01156 
01157   // Shouldn't happen: Ignore "unknown" notification method
01158   if (e->m_mode == UnknownMode) return NoChange;
01159 
01160 #if defined ( HAVE_DNOTIFY ) || defined( HAVE_INOTIFY )
01161   if (e->m_mode == DNotifyMode || e->m_mode == INotifyMode ) {
01162     // we know nothing has changed, no need to stat
01163     if(!e->dirty) return NoChange;
01164     kdDebug(7001) << "scanning " << e->path << " " << e->m_status << " " << e->m_ctime << endl;
01165     e->dirty = false;
01166   }
01167 #endif
01168 
01169   if (e->m_mode == StatMode) {
01170     // only scan if timeout on entry timer happens;
01171     // e.g. when using 500msec global timer, a entry
01172     // with freq=5000 is only watched every 10th time
01173 
01174     e->msecLeft -= freq;
01175     if (e->msecLeft>0) return NoChange;
01176     e->msecLeft += e->freq;
01177   }
01178 
01179   KDE_struct_stat stat_buf;
01180   bool exists = (KDE_stat(QFile::encodeName(e->path), &stat_buf) == 0);
01181   if (exists) {
01182 
01183     if (e->m_status == NonExistent) {
01184       e->m_ctime = stat_buf.st_ctime;
01185       e->m_status = Normal;
01186       e->m_nlink = stat_buf.st_nlink;
01187       return Created;
01188     }
01189 
01190     if ( (e->m_ctime != invalid_ctime) &&
01191      ((stat_buf.st_ctime != e->m_ctime) ||
01192       (stat_buf.st_nlink != (nlink_t) e->m_nlink)) ) {
01193       e->m_ctime = stat_buf.st_ctime;
01194       e->m_nlink = stat_buf.st_nlink;
01195       return Changed;
01196     }
01197 
01198     return NoChange;
01199   }
01200 
01201   // dir/file doesn't exist
01202 
01203   if (e->m_ctime == invalid_ctime && e->m_status == NonExistent) {
01204     e->m_nlink = 0;
01205     e->m_status = NonExistent;
01206     return NoChange;
01207   }
01208 
01209   e->m_ctime = invalid_ctime;
01210   e->m_nlink = 0;
01211   e->m_status = NonExistent;
01212 
01213   return Deleted;
01214 }
01215 
01216 /* Notify all interested KDirWatch instances about a given event on an entry
01217  * and stored pending events. When watching is stopped, the event is
01218  * added to the pending events.
01219  */
01220 void KDirWatchPrivate::emitEvent(Entry* e, int event, const QString &fileName)
01221 {
01222   QString path = e->path;
01223   if (!fileName.isEmpty()) {
01224     if (!QDir::isRelativePath(fileName))
01225       path = fileName;
01226     else
01227 #ifdef Q_OS_UNIX
01228       path += "/" + fileName;
01229 #elif defined(Q_WS_WIN)
01230       //current drive is passed instead of /
01231       path += QDir::currentDirPath().left(2) + "/" + fileName;
01232 #endif
01233   }
01234 
01235   QPtrListIterator<Client> cit( e->m_clients );
01236   for ( ; cit.current(); ++cit )
01237   {
01238     Client* c = cit.current();
01239 
01240     if (c->instance==0 || c->count==0) continue;
01241 
01242     if (c->watchingStopped) {
01243       // add event to pending...
01244       if (event == Changed)
01245     c->pending |= event;
01246       else if (event == Created || event == Deleted)
01247     c->pending = event;
01248       continue;
01249     }
01250     // not stopped
01251     if (event == NoChange || event == Changed)
01252       event |= c->pending;
01253     c->pending = NoChange;
01254     if (event == NoChange) continue;
01255 
01256     if (event & Deleted) {
01257       c->instance->setDeleted(path);
01258       // emit only Deleted event...
01259       continue;
01260     }
01261 
01262     if (event & Created) {
01263       c->instance->setCreated(path);
01264       // possible emit Change event after creation
01265     }
01266 
01267     if (event & Changed)
01268       c->instance->setDirty(path);
01269   }
01270 }
01271 
01272 // Remove entries which were marked to be removed
01273 void KDirWatchPrivate::slotRemoveDelayed()
01274 {
01275   Entry* e;
01276   delayRemove = false;
01277   for(e=removeList.first();e;e=removeList.next())
01278     removeEntry(0, e->path, 0);
01279   removeList.clear();
01280 }
01281 
01282 /* Scan all entries to be watched for changes. This is done regularly
01283  * when polling and once after a DNOTIFY signal. This is NOT used by FAM.
01284  */
01285 void KDirWatchPrivate::slotRescan()
01286 {
01287   EntryMap::Iterator it;
01288 
01289   // People can do very long things in the slot connected to dirty(),
01290   // like showing a message box. We don't want to keep polling during
01291   // that time, otherwise the value of 'delayRemove' will be reset.
01292   bool timerRunning = timer->isActive();
01293   if ( timerRunning )
01294     timer->stop();
01295 
01296   // We delay deletions of entries this way.
01297   // removeDir(), when called in slotDirty(), can cause a crash otherwise
01298   delayRemove = true;
01299 
01300 #if defined(HAVE_DNOTIFY) || defined(HAVE_INOTIFY)
01301   QPtrList<Entry> dList, cList;
01302 #endif
01303 
01304   if (rescan_all)
01305   {
01306     // mark all as dirty
01307     it = m_mapEntries.begin();
01308     for( ; it != m_mapEntries.end(); ++it )
01309       (*it).dirty = true;
01310     rescan_all = false;
01311   }
01312   else
01313   {
01314     // progate dirty flag to dependant entries (e.g. file watches)
01315     it = m_mapEntries.begin();
01316     for( ; it != m_mapEntries.end(); ++it )
01317       if (((*it).m_mode == INotifyMode || (*it).m_mode == DNotifyMode) && (*it).dirty )
01318         (*it).propagate_dirty();
01319   }
01320 
01321   it = m_mapEntries.begin();
01322   for( ; it != m_mapEntries.end(); ++it ) {
01323     // we don't check invalid entries (i.e. remove delayed)
01324     if (!(*it).isValid()) continue;
01325 
01326     int ev = scanEntry( &(*it) );
01327 
01328 
01329 #ifdef HAVE_INOTIFY
01330     if ((*it).m_mode == INotifyMode && ev == Created && (*it).wd == 0) {
01331       cList.append( &(*it) );
01332       if (! useINotify( &(*it) )) {
01333         useStat( &(*it) );
01334       }
01335     }
01336 #endif
01337 
01338 #ifdef HAVE_DNOTIFY
01339     if ((*it).m_mode == DNotifyMode) {
01340       if ((*it).isDir && (ev == Deleted)) {
01341     dList.append( &(*it) );
01342 
01343     // must close the FD.
01344     if ((*it).dn_fd) {
01345       ::close((*it).dn_fd);
01346       fd_Entry.remove((*it).dn_fd);
01347       (*it).dn_fd = 0;
01348     }
01349       }
01350 
01351       else if ((*it).isDir && (ev == Created)) {
01352     // For created, but yet without DNOTIFYing ...
01353     if ( (*it).dn_fd == 0) {
01354       cList.append( &(*it) );
01355       if (! useDNotify( &(*it) )) {
01356         // if DNotify setup fails...
01357         useStat( &(*it) );
01358       }
01359     }
01360       }
01361     }
01362 #endif
01363 
01364     if ( ev != NoChange )
01365       emitEvent( &(*it), ev);
01366   }
01367 
01368 
01369 #if defined(HAVE_DNOTIFY) || defined(HAVE_INOTIFY)
01370   // Scan parent of deleted directories for new creation
01371   Entry* e;
01372   for(e=dList.first();e;e=dList.next())
01373     addEntry(0, QDir::cleanDirPath( e->path+"/.."), e, true);
01374 
01375   // Remove watch of parent of new created directories
01376   for(e=cList.first();e;e=cList.next())
01377     removeEntry(0, QDir::cleanDirPath( e->path+"/.."), e);
01378 #endif
01379 
01380   if ( timerRunning )
01381     timer->start(freq);
01382 
01383   QTimer::singleShot(0, this, SLOT(slotRemoveDelayed()));
01384 }
01385 
01386 bool KDirWatchPrivate::isNoisyFile( const char * filename )
01387 {
01388   // $HOME/.X.err grows with debug output, so don't notify change
01389   if ( *filename == '.') {
01390     if (strncmp(filename, ".X.err", 6) == 0) return true;
01391     if (strncmp(filename, ".xsession-errors", 16) == 0) return true;
01392     // fontconfig updates the cache on every KDE app start
01393     // (inclusive kio_thumbnail slaves)
01394     if (strncmp(filename, ".fonts.cache", 12) == 0) return true;
01395   }
01396 
01397   return false;
01398 }
01399 
01400 #ifdef HAVE_FAM
01401 void KDirWatchPrivate::famEventReceived()
01402 {
01403   static FAMEvent fe;
01404 
01405   delayRemove = true;
01406 
01407   while(use_fam && FAMPending(&fc)) {
01408     if (FAMNextEvent(&fc, &fe) == -1) {
01409       kdWarning(7001) << "FAM connection problem, switching to polling."
01410               << endl;
01411       use_fam = false;
01412       delete sn; sn = 0;
01413 
01414       // Replace all FAMMode entries with DNotify/Stat
01415       EntryMap::Iterator it;
01416       it = m_mapEntries.begin();
01417       for( ; it != m_mapEntries.end(); ++it )
01418     if ((*it).m_mode == FAMMode && (*it).m_clients.count()>0) {
01419 #ifdef HAVE_INOTIFY
01420       if (useINotify( &(*it) )) continue;
01421 #endif
01422 #ifdef HAVE_DNOTIFY
01423       if (useDNotify( &(*it) )) continue;
01424 #endif
01425       useStat( &(*it) );
01426     }
01427     }
01428     else
01429       checkFAMEvent(&fe);
01430   }
01431 
01432   QTimer::singleShot(0, this, SLOT(slotRemoveDelayed()));
01433 }
01434 
01435 void KDirWatchPrivate::checkFAMEvent(FAMEvent* fe)
01436 {
01437   // Don't be too verbose ;-)
01438   if ((fe->code == FAMExists) ||
01439       (fe->code == FAMEndExist) ||
01440       (fe->code == FAMAcknowledge)) return;
01441 
01442   if ( isNoisyFile( fe->filename ) )
01443     return;
01444 
01445   Entry* e = 0;
01446   EntryMap::Iterator it = m_mapEntries.begin();
01447   for( ; it != m_mapEntries.end(); ++it )
01448     if (FAMREQUEST_GETREQNUM(&( (*it).fr )) ==
01449        FAMREQUEST_GETREQNUM(&(fe->fr)) ) {
01450       e = &(*it);
01451       break;
01452     }
01453 
01454   // Entry* e = static_cast<Entry*>(fe->userdata);
01455 
01456 #if 0 // #88538
01457   kdDebug(7001) << "Processing FAM event ("
01458         << ((fe->code == FAMChanged) ? "FAMChanged" :
01459             (fe->code == FAMDeleted) ? "FAMDeleted" :
01460             (fe->code == FAMStartExecuting) ? "FAMStartExecuting" :
01461             (fe->code == FAMStopExecuting) ? "FAMStopExecuting" :
01462             (fe->code == FAMCreated) ? "FAMCreated" :
01463             (fe->code == FAMMoved) ? "FAMMoved" :
01464             (fe->code == FAMAcknowledge) ? "FAMAcknowledge" :
01465             (fe->code == FAMExists) ? "FAMExists" :
01466             (fe->code == FAMEndExist) ? "FAMEndExist" : "Unknown Code")
01467         << ", " << fe->filename
01468         << ", Req " << FAMREQUEST_GETREQNUM(&(fe->fr))
01469         << ")" << endl;
01470 #endif
01471 
01472   if (!e) {
01473     // this happens e.g. for FAMAcknowledge after deleting a dir...
01474     //    kdDebug(7001) << "No entry for FAM event ?!" << endl;
01475     return;
01476   }
01477 
01478   if (e->m_status == NonExistent) {
01479     kdDebug(7001) << "FAM event for nonExistent entry " << e->path << endl;
01480     return;
01481   }
01482 
01483   // Delayed handling. This rechecks changes with own stat calls.
01484   e->dirty = true;
01485   if (!rescan_timer.isActive())
01486     rescan_timer.start(m_PollInterval, true);
01487 
01488   // needed FAM control actions on FAM events
01489   if (e->isDir)
01490     switch (fe->code)
01491     {
01492       case FAMDeleted:
01493        // file absolute: watched dir
01494         if (!QDir::isRelativePath(fe->filename))
01495         {
01496           // a watched directory was deleted
01497 
01498           e->m_status = NonExistent;
01499           FAMCancelMonitor(&fc, &(e->fr) ); // needed ?
01500           kdDebug(7001) << "Cancelled FAMReq "
01501                         << FAMREQUEST_GETREQNUM(&(e->fr))
01502                         << " for " << e->path << endl;
01503           // Scan parent for a new creation
01504           addEntry(0, QDir::cleanDirPath( e->path+"/.."), e, true);
01505         }
01506         break;
01507 
01508       case FAMCreated: {
01509           // check for creation of a directory we have to watch
01510           Entry *sub_entry = e->m_entries.first();
01511           for(;sub_entry; sub_entry = e->m_entries.next())
01512             if (sub_entry->path == e->path + "/" + fe->filename) break;
01513           if (sub_entry && sub_entry->isDir) {
01514             QString path = e->path;
01515             removeEntry(0,e->path,sub_entry); // <e> can be invalid here!!
01516             sub_entry->m_status = Normal;
01517             if (!useFAM(sub_entry))
01518 #ifdef HAVE_INOTIFY
01519               if (!useINotify(sub_entry ))
01520 #endif
01521                 useStat(sub_entry);
01522           }
01523           break;
01524         }
01525 
01526       default:
01527         break;
01528     }
01529 }
01530 #else
01531 void KDirWatchPrivate::famEventReceived() {}
01532 #endif
01533 
01534 
01535 void KDirWatchPrivate::statistics()
01536 {
01537   EntryMap::Iterator it;
01538 
01539   kdDebug(7001) << "Entries watched:" << endl;
01540   if (m_mapEntries.count()==0) {
01541     kdDebug(7001) << "  None." << endl;
01542   }
01543   else {
01544     it = m_mapEntries.begin();
01545     for( ; it != m_mapEntries.end(); ++it ) {
01546       Entry* e = &(*it);
01547       kdDebug(7001) << "  " << e->path << " ("
01548             << ((e->m_status==Normal)?"":"Nonexistent ")
01549             << (e->isDir ? "Dir":"File") << ", using "
01550             << ((e->m_mode == FAMMode) ? "FAM" :
01551                         (e->m_mode == INotifyMode) ? "INotify" :
01552             (e->m_mode == DNotifyMode) ? "DNotify" :
01553             (e->m_mode == StatMode) ? "Stat" : "Unknown Method")
01554             << ")" << endl;
01555 
01556       Client* c = e->m_clients.first();
01557       for(;c; c = e->m_clients.next()) {
01558     QString pending;
01559     if (c->watchingStopped) {
01560       if (c->pending & Deleted) pending += "deleted ";
01561       if (c->pending & Created) pending += "created ";
01562       if (c->pending & Changed) pending += "changed ";
01563       if (!pending.isEmpty()) pending = " (pending: " + pending + ")";
01564       pending = ", stopped" + pending;
01565     }
01566     kdDebug(7001) << "    by " << c->instance->name()
01567               << " (" << c->count << " times)"
01568               << pending << endl;
01569       }
01570       if (e->m_entries.count()>0) {
01571     kdDebug(7001) << "    dependent entries:" << endl;
01572     Entry* d = e->m_entries.first();
01573     for(;d; d = e->m_entries.next()) {
01574           kdDebug(7001) << "      " << d << endl;
01575       kdDebug(7001) << "      " << d->path << " (" << d << ") " << endl;
01576     }
01577       }
01578     }
01579   }
01580 }
01581 
01582 
01583 //
01584 // Class KDirWatch
01585 //
01586 
01587 static KStaticDeleter<KDirWatch> sd_dw;
01588 KDirWatch* KDirWatch::s_pSelf = 0L;
01589 
01590 KDirWatch* KDirWatch::self()
01591 {
01592   if ( !s_pSelf ) {
01593     sd_dw.setObject( s_pSelf, new KDirWatch );
01594   }
01595 
01596   return s_pSelf;
01597 }
01598 
01599 bool KDirWatch::exists()
01600 {
01601   return s_pSelf != 0;
01602 }
01603 
01604 KDirWatch::KDirWatch (QObject* parent, const char* name)
01605   : QObject(parent,name)
01606 {
01607   if (!name) {
01608     static int nameCounter = 0;
01609 
01610     nameCounter++;
01611     setName(QString("KDirWatch-%1").arg(nameCounter).ascii());
01612   }
01613 
01614   if (!dwp_self)
01615     dwp_self = new KDirWatchPrivate;
01616   d = dwp_self;
01617   d->ref();
01618 
01619   _isStopped = false;
01620 }
01621 
01622 KDirWatch::~KDirWatch()
01623 {
01624   d->removeEntries(this);
01625   if ( d->deref() )
01626   {
01627     // delete it if it's the last one
01628     delete d;
01629     dwp_self = 0L;
01630   }
01631 }
01632 
01633 
01634 // TODO: add watchFiles/recursive support
01635 void KDirWatch::addDir( const QString& _path,
01636             bool watchFiles, bool recursive)
01637 {
01638   if (watchFiles || recursive) {
01639     kdDebug(7001) << "addDir - recursive/watchFiles not supported yet in KDE 3.x" << endl;
01640   }
01641   if (d) d->addEntry(this, _path, 0, true);
01642 }
01643 
01644 void KDirWatch::addFile( const QString& _path )
01645 {
01646   if (d) d->addEntry(this, _path, 0, false);
01647 }
01648 
01649 QDateTime KDirWatch::ctime( const QString &_path )
01650 {
01651   KDirWatchPrivate::Entry* e = d->entry(_path);
01652 
01653   if (!e)
01654     return QDateTime();
01655 
01656   QDateTime result;
01657   result.setTime_t(e->m_ctime);
01658   return result;
01659 }
01660 
01661 void KDirWatch::removeDir( const QString& _path )
01662 {
01663   if (d) d->removeEntry(this, _path, 0);
01664 }
01665 
01666 void KDirWatch::removeFile( const QString& _path )
01667 {
01668   if (d) d->removeEntry(this, _path, 0);
01669 }
01670 
01671 bool KDirWatch::stopDirScan( const QString& _path )
01672 {
01673   if (d) {
01674     KDirWatchPrivate::Entry *e = d->entry(_path);
01675     if (e && e->isDir) return d->stopEntryScan(this, e);
01676   }
01677   return false;
01678 }
01679 
01680 bool KDirWatch::restartDirScan( const QString& _path )
01681 {
01682   if (d) {
01683     KDirWatchPrivate::Entry *e = d->entry(_path);
01684     if (e && e->isDir)
01685       // restart without notifying pending events
01686       return d->restartEntryScan(this, e, false);
01687   }
01688   return false;
01689 }
01690 
01691 void KDirWatch::stopScan()
01692 {
01693   if (d) d->stopScan(this);
01694   _isStopped = true;
01695 }
01696 
01697 void KDirWatch::startScan( bool notify, bool skippedToo )
01698 {
01699   _isStopped = false;
01700   if (d) d->startScan(this, notify, skippedToo);
01701 }
01702 
01703 
01704 bool KDirWatch::contains( const QString& _path ) const
01705 {
01706   KDirWatchPrivate::Entry* e = d->entry(_path);
01707   if (!e)
01708      return false;
01709 
01710   KDirWatchPrivate::Client* c = e->m_clients.first();
01711   for(;c;c=e->m_clients.next())
01712     if (c->instance == this) return true;
01713 
01714   return false;
01715 }
01716 
01717 void KDirWatch::statistics()
01718 {
01719   if (!dwp_self) {
01720     kdDebug(7001) << "KDirWatch not used" << endl;
01721     return;
01722   }
01723   dwp_self->statistics();
01724 }
01725 
01726 
01727 void KDirWatch::setCreated( const QString & _file )
01728 {
01729   kdDebug(7001) << name() << " emitting created " << _file << endl;
01730   emit created( _file );
01731 }
01732 
01733 void KDirWatch::setDirty( const QString & _file )
01734 {
01735   kdDebug(7001) << name() << " emitting dirty " << _file << endl;
01736   emit dirty( _file );
01737 }
01738 
01739 void KDirWatch::setDeleted( const QString & _file )
01740 {
01741   kdDebug(7001) << name() << " emitting deleted " << _file << endl;
01742   emit deleted( _file );
01743 }
01744 
01745 KDirWatch::Method KDirWatch::internalMethod()
01746 {
01747 #ifdef HAVE_FAM
01748   if (d->use_fam)
01749     return KDirWatch::FAM;
01750 #endif
01751 #ifdef HAVE_INOTIFY
01752   if (d->supports_inotify)
01753     return KDirWatch::INotify;
01754 #endif
01755 #ifdef HAVE_DNOTIFY
01756   if (d->supports_dnotify)
01757     return KDirWatch::DNotify;
01758 #endif
01759   return KDirWatch::Stat;
01760 }
01761 
01762 
01763 #include "kdirwatch.moc"
01764 #include "kdirwatch_p.moc"
01765 
01766 //sven
01767 
01768 // vim: sw=2 ts=8 et
KDE Home | KDE Accessibility Home | Description of Access Keys