kdirwatch.cpp

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