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