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