kdirwatch.cpp

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