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