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