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