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