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