author | zautrix <zautrix> | 2004-08-08 12:30:41 (UTC) |
---|---|---|
committer | zautrix <zautrix> | 2004-08-08 12:30:41 (UTC) |
commit | 75138e84a2271f8929478853151a6ed4a73fb2cc (patch) (side-by-side diff) | |
tree | 804b3277bf2e4c4e371611677481803f527bf90e | |
parent | 4cc869512488b72304c7cbb5526c6f4cc957e677 (diff) | |
download | kdepimpi-75138e84a2271f8929478853151a6ed4a73fb2cc.zip kdepimpi-75138e84a2271f8929478853151a6ed4a73fb2cc.tar.gz kdepimpi-75138e84a2271f8929478853151a6ed4a73fb2cc.tar.bz2 |
Fixed kdirwatch
-rw-r--r-- | microkde/kio/kio/kdirwatch.cpp | 3 |
1 files changed, 2 insertions, 1 deletions
diff --git a/microkde/kio/kio/kdirwatch.cpp b/microkde/kio/kio/kdirwatch.cpp index 98d24e0..1596d1f 100644 --- a/microkde/kio/kio/kdirwatch.cpp +++ b/microkde/kio/kio/kdirwatch.cpp @@ -214,769 +214,770 @@ KDirWatchPrivate::KDirWatchPrivate() struct utsname uts; int major, minor, patch; if (uname(&uts) < 0) supports_dnotify = false; // *shrug* else if (sscanf(uts.release, "%d.%d.%d", &major, &minor, &patch) != 3) supports_dnotify = false; // *shrug* else if( major * 1000000 + minor * 1000 + patch < 2004019 ) { // <2.4.19 kdDebug(7001) << "Can't use DNotify, Linux kernel too old" << endl; supports_dnotify = false; } if( supports_dnotify ) { available += ", DNotify"; pipe(mPipe); fcntl(mPipe[0], F_SETFD, FD_CLOEXEC); fcntl(mPipe[1], F_SETFD, FD_CLOEXEC); mSn = new QSocketNotifier( mPipe[0], QSocketNotifier::Read, this); connect(mSn, SIGNAL(activated(int)), this, SLOT(slotActivated())); connect(&mTimer, SIGNAL(timeout()), this, SLOT(slotRescan())); struct sigaction act; act.sa_sigaction = KDirWatchPrivate::dnotify_handler; sigemptyset(&act.sa_mask); act.sa_flags = SA_SIGINFO; #ifdef SA_RESTART act.sa_flags |= SA_RESTART; #endif if( dnotify_signal == 0 ) dnotify_signal = SIGRTMIN + 8; sigaction(dnotify_signal, &act, NULL); act.sa_sigaction = KDirWatchPrivate::dnotify_sigio_handler; sigaction(SIGIO, &act, &old_sigio_act); } #endif kdDebug(7001) << "Available methods: " << available << endl; } /* This should never be called, but doesn't harm */ KDirWatchPrivate::~KDirWatchPrivate() { timer->stop(); /* remove all entries being watched */ removeEntries(0); #ifdef HAVE_FAM if (use_fam) { FAMClose(&fc); kdDebug(7001) << "KDirWatch deleted (FAM closed)" << endl; } #endif } #ifdef HAVE_DNOTIFY void KDirWatchPrivate::slotActivated() { char dummy_buf[100]; read(mPipe[0], &dummy_buf, 100); if (!mTimer.isActive()) mTimer.start(200, true); } /* In DNOTIFY mode, only entries which are marked dirty are scanned. * We first need to mark all yet nonexistant, but possible created * entries as dirty... */ void KDirWatchPrivate::Entry::propagate_dirty() { Entry* sub_entry; for(sub_entry = m_entries.first(); sub_entry; sub_entry = m_entries.next()) { if (!sub_entry->dn_dirty) { sub_entry->dn_dirty = true; sub_entry->propagate_dirty(); } } } #else // !HAVE_DNOTIFY // slots always have to be defined... void KDirWatchPrivate::slotActivated() {} #endif /* A KDirWatch instance is interested in getting events for * this file/Dir entry. */ void KDirWatchPrivate::Entry::addClient(KDirWatch* instance) { Client* client = m_clients.first(); for(;client; client = m_clients.next()) if (client->instance == instance) break; if (client) { client->count++; return; } client = new Client; client->instance = instance; client->count = 1; client->watchingStopped = instance->isStopped(); client->pending = NoChange; m_clients.append(client); } void KDirWatchPrivate::Entry::removeClient(KDirWatch* instance) { Client* client = m_clients.first(); for(;client; client = m_clients.next()) if (client->instance == instance) break; if (client) { client->count--; if (client->count == 0) { m_clients.removeRef(client); delete client; } } } /* get number of clients */ int KDirWatchPrivate::Entry::clients() { int clients = 0; Client* client = m_clients.first(); for(;client; client = m_clients.next()) clients += client->count; return clients; } KDirWatchPrivate::Entry* KDirWatchPrivate::entry(const QString& _path) { // we only support absolute paths if (_path.left(1) != "/") { return 0; } QString path = _path; if ( path.length() > 1 && path.right(1) == "/" ) path.truncate( path.length() - 1 ); EntryMap::Iterator it = m_mapEntries.find( path ); if ( it == m_mapEntries.end() ) return 0; else return &(*it); } // set polling frequency for a entry and adjust global freq if needed void KDirWatchPrivate::useFreq(Entry* e, int newFreq) { e->freq = newFreq; // a reasonable frequency for the global polling timer if (e->freq < freq) { freq = e->freq; if (timer->isActive()) timer->changeInterval(freq); kdDebug(7001) << "Global Poll Freq is now " << freq << " msec" << endl; } } #if defined(HAVE_FAM) // setup FAM notification, returns false if not possible bool KDirWatchPrivate::useFAM(Entry* e) { if (!use_fam) return false; e->m_mode = FAMMode; if (e->isDir) { if (e->m_status == NonExistent) { // If the directory does not exist we watch the parent directory addEntry(0, QDir::cleanDirPath(e->path+"/.."), e, true); } else { int res =FAMMonitorDirectory(&fc, QFile::encodeName(e->path), &(e->fr), e); if (res<0) { e->m_mode = UnknownMode; use_fam=false; return false; } kdDebug(7001) << " Setup FAM (Req " << FAMREQUEST_GETREQNUM(&(e->fr)) << ") for " << e->path << endl; } } else { if (e->m_status == NonExistent) { // If the file does not exist we watch the directory addEntry(0, QFileInfo(e->path).dirPath(true), e, true); } else { int res = FAMMonitorFile(&fc, QFile::encodeName(e->path), &(e->fr), e); if (res<0) { e->m_mode = UnknownMode; use_fam=false; return false; } kdDebug(7001) << " Setup FAM (Req " << FAMREQUEST_GETREQNUM(&(e->fr)) << ") for " << e->path << endl; } } // handle FAM events to avoid deadlock // (FAM sends back all files in a directory when monitoring) famEventReceived(); return true; } #endif #ifdef HAVE_DNOTIFY // setup DNotify notification, returns false if not possible bool KDirWatchPrivate::useDNotify(Entry* e) { e->dn_fd = 0; if (!supports_dnotify) return false; e->m_mode = DNotifyMode; if (e->isDir) { e->dn_dirty = false; if (e->m_status == Normal) { int fd = open(QFile::encodeName(e->path).data(), O_RDONLY); // Migrate fd to somewhere above 128. Some libraries have // constructs like: // fd = socket(...) // if (fd > ARBITRARY_LIMIT) // return error; // // Since programs might end up using a lot of KDirWatch objects // for a rather long time the above braindamage could get // triggered. // // By moving the kdirwatch fd's to > 128, calls like socket() will keep // returning fd's < ARBITRARY_LIMIT for a bit longer. int fd2 = fcntl(fd, F_DUPFD, 128); if (fd2 >= 0) { close(fd); fd = fd2; } if (fd<0) { e->m_mode = UnknownMode; return false; } int mask = DN_DELETE|DN_CREATE|DN_RENAME|DN_MULTISHOT; // if dependant is a file watch, we check for MODIFY & ATTRIB too for(Entry* dep=e->m_entries.first();dep;dep=e->m_entries.next()) if (!dep->isDir) { mask |= DN_MODIFY|DN_ATTRIB; break; } if(fcntl(fd, F_SETSIG, dnotify_signal) < 0 || fcntl(fd, F_NOTIFY, mask) < 0) { kdDebug(7001) << "Not using Linux Directory Notifications." << endl; supports_dnotify = false; ::close(fd); e->m_mode = UnknownMode; return false; } fd_Entry.replace(fd, e); e->dn_fd = fd; kdDebug(7001) << " Setup DNotify (fd " << fd << ") for " << e->path << endl; } else { // NotExisting addEntry(0, QDir::cleanDirPath(e->path+"/.."), e, true); } } else { // File // we always watch the directory (DNOTIFY can't watch files alone) // this notifies us about changes of files therein addEntry(0, QFileInfo(e->path).dirPath(true), e, true); } return true; } #endif bool KDirWatchPrivate::useStat(Entry* e) { //US we have no KIO::probably_slow_mounted. So disable this part //US if (KIO::probably_slow_mounted(e->path)) //US useFreq(e, m_nfsPollInterval); //US else useFreq(e, m_PollInterval); if (e->m_mode != StatMode) { e->m_mode = StatMode; statEntries++; if ( statEntries == 1 ) { // if this was first STAT entry (=timer was stopped) timer->start(freq); // then start the timer kdDebug(7001) << " Started Polling Timer, freq " << freq << endl; } } kdDebug(7001) << " Setup Stat (freq " << e->freq << ") for " << e->path << endl; return true; } /* If <instance> !=0, this KDirWatch instance wants to watch at <_path>, * providing in <isDir> the type of the entry to be watched. * Sometimes, entries are dependant on each other: if <sub_entry> !=0, * this entry needs another entry to watch himself (when notExistent). */ void KDirWatchPrivate::addEntry(KDirWatch* instance, const QString& _path, Entry* sub_entry, bool isDir) { QString path = _path; if (path.startsWith("/dev/") || (path == "/dev")) return; // Don't even go there. if ( path.length() > 1 && path.right(1) == "/" ) path.truncate( path.length() - 1 ); EntryMap::Iterator it = m_mapEntries.find( path ); if ( it != m_mapEntries.end() ) { if (sub_entry) { (*it).m_entries.append(sub_entry); kdDebug(7001) << "Added already watched Entry " << path << " (for " << sub_entry->path << ")" << endl; #ifdef HAVE_DNOTIFY Entry* e = &(*it); if( e->dn_fd > 0 ) { int mask = DN_DELETE|DN_CREATE|DN_RENAME|DN_MULTISHOT; // if dependant is a file watch, we check for MODIFY & ATTRIB too for(Entry* dep=e->m_entries.first();dep;dep=e->m_entries.next()) if (!dep->isDir) { mask |= DN_MODIFY|DN_ATTRIB; break; } if( fcntl(e->dn_fd, F_NOTIFY, mask) < 0) { // shouldn't happen ::close(e->dn_fd); e->m_mode = UnknownMode; fd_Entry.remove(e->dn_fd); e->dn_fd = 0; useStat( e ); } } #endif } else { (*it).addClient(instance); kdDebug(7001) << "Added already watched Entry " << path << " (now " << (*it).clients() << " clients)" << QString(" [%1]").arg(instance->name()) << endl; } return; } // we have a new path to watch struct stat stat_buf; bool exists = (stat(QFile::encodeName(path), &stat_buf) == 0); Entry newEntry; m_mapEntries.insert( path, newEntry ); // the insert does a copy, so we have to use <e> now Entry* e = &(m_mapEntries[path]); if (exists) { - e->isDir = S_ISDIR(stat_buf.st_mode); + QFileInfo fi ( path ); + e->isDir = fi.isDir(); if (e->isDir && !isDir) qWarning("KDirWatch: %s is a directory. Use addDir!", path.ascii()); else if (!e->isDir && isDir) qWarning("KDirWatch: %s is a file. Use addFile!", path.ascii()); e->m_ctime = stat_buf.st_ctime; e->m_status = Normal; e->m_nlink = stat_buf.st_nlink; } else { e->isDir = isDir; e->m_ctime = invalid_ctime; e->m_status = NonExistent; e->m_nlink = 0; } e->path = path; if (sub_entry) e->m_entries.append(sub_entry); else e->addClient(instance); kdDebug(7001) << "Added " << (e->isDir ? "Dir ":"File ") << path << (e->m_status == NonExistent ? " NotExisting" : "") << (sub_entry ? QString(" for %1").arg(sub_entry->path) : QString("")) << (instance ? QString(" [%1]").arg(instance->name()) : QString("")) << endl; // now setup the notification method e->m_mode = UnknownMode; e->msecLeft = 0; #if defined(HAVE_FAM) if (useFAM(e)) return; #endif #ifdef HAVE_DNOTIFY if (useDNotify(e)) return; #endif useStat(e); } void KDirWatchPrivate::removeEntry( KDirWatch* instance, const QString& _path, Entry* sub_entry ) { Entry* e = entry(_path); if (!e) { kdWarning(7001) << "KDirWatch::removeDir can't handle '" << _path << "'" << endl; return; } if (sub_entry) e->m_entries.removeRef(sub_entry); else e->removeClient(instance); if (e->m_clients.count() || e->m_entries.count()) return; if (delayRemove) { // removeList is allowed to contain any entry at most once if (removeList.findRef(e)==-1) removeList.append(e); // now e->isValid() is false return; } #ifdef HAVE_FAM if (e->m_mode == FAMMode) { if ( e->m_status == Normal) { FAMCancelMonitor(&fc, &(e->fr) ); kdDebug(7001) << "Cancelled FAM (Req " << FAMREQUEST_GETREQNUM(&(e->fr)) << ") for " << e->path << endl; } else { if (e->isDir) removeEntry(0, QDir::cleanDirPath(e->path+"/.."), e); else removeEntry(0, QFileInfo(e->path).dirPath(true), e); } } #endif #ifdef HAVE_DNOTIFY if (e->m_mode == DNotifyMode) { if (!e->isDir) { removeEntry(0, QFileInfo(e->path).dirPath(true), e); } else { // isDir // must close the FD. if ( e->m_status == Normal) { if (e->dn_fd) { ::close(e->dn_fd); fd_Entry.remove(e->dn_fd); kdDebug(7001) << "Cancelled DNotify (fd " << e->dn_fd << ") for " << e->path << endl; e->dn_fd = 0; } } else { removeEntry(0, QDir::cleanDirPath(e->path+"/.."), e); } } } #endif if (e->m_mode == StatMode) { statEntries--; if ( statEntries == 0 ) { timer->stop(); // stop timer if lists are empty kdDebug(7001) << " Stopped Polling Timer" << endl; } } kdDebug(7001) << "Removed " << (e->isDir ? "Dir ":"File ") << e->path << (sub_entry ? QString(" for %1").arg(sub_entry->path) : QString("")) << (instance ? QString(" [%1]").arg(instance->name()) : QString("")) << endl; m_mapEntries.remove( e->path ); // <e> not valid any more } /* Called from KDirWatch destructor: * remove <instance> as client from all entries */ void KDirWatchPrivate::removeEntries( KDirWatch* instance ) { QPtrList<Entry> list; int minfreq = 3600000; // put all entries where instance is a client in list EntryMap::Iterator it = m_mapEntries.begin(); for( ; it != m_mapEntries.end(); ++it ) { Client* c = (*it).m_clients.first(); for(;c;c=(*it).m_clients.next()) if (c->instance == instance) break; if (c) { c->count = 1; // forces deletion of instance as client list.append(&(*it)); } else if ( (*it).m_mode == StatMode && (*it).freq < minfreq ) minfreq = (*it).freq; } for(Entry* e=list.first();e;e=list.next()) removeEntry(instance, e->path, 0); if (minfreq > freq) { // we can decrease the global polling frequency freq = minfreq; if (timer->isActive()) timer->changeInterval(freq); kdDebug(7001) << "Poll Freq now " << freq << " msec" << endl; } } // instance ==0: stop scanning for all instances bool KDirWatchPrivate::stopEntryScan( KDirWatch* instance, Entry* e) { int stillWatching = 0; Client* c = e->m_clients.first(); for(;c;c=e->m_clients.next()) { if (!instance || instance == c->instance) c->watchingStopped = true; else if (!c->watchingStopped) stillWatching += c->count; } kdDebug(7001) << instance->name() << " stopped scanning " << e->path << " (now " << stillWatching << " watchers)" << endl; if (stillWatching == 0) { // if nobody is interested, we don't watch e->m_ctime = invalid_ctime; // invalid // e->m_status = Normal; } return true; } // instance ==0: start scanning for all instances bool KDirWatchPrivate::restartEntryScan( KDirWatch* instance, Entry* e, bool notify) { int wasWatching = 0, newWatching = 0; Client* c = e->m_clients.first(); for(;c;c=e->m_clients.next()) { if (!c->watchingStopped) wasWatching += c->count; else if (!instance || instance == c->instance) { c->watchingStopped = false; newWatching += c->count; } } if (newWatching == 0) return false; kdDebug(7001) << instance->name() << " restarted scanning " << e->path << " (now " << wasWatching+newWatching << " watchers)" << endl; // restart watching and emit pending events int ev = NoChange; if (wasWatching == 0) { if (!notify) { struct stat stat_buf; bool exists = (stat(QFile::encodeName(e->path), &stat_buf) == 0); if (exists) { e->m_ctime = stat_buf.st_ctime; e->m_status = Normal; e->m_nlink = stat_buf.st_nlink; } else { e->m_ctime = invalid_ctime; e->m_status = NonExistent; e->m_nlink = 0; } } e->msecLeft = 0; ev = scanEntry(e); } emitEvent(e,ev); return true; } // instance ==0: stop scanning for all instances void KDirWatchPrivate::stopScan(KDirWatch* instance) { EntryMap::Iterator it = m_mapEntries.begin(); for( ; it != m_mapEntries.end(); ++it ) stopEntryScan(instance, &(*it)); } void KDirWatchPrivate::startScan(KDirWatch* instance, bool notify, bool skippedToo ) { if (!notify) resetList(instance,skippedToo); EntryMap::Iterator it = m_mapEntries.begin(); for( ; it != m_mapEntries.end(); ++it ) restartEntryScan(instance, &(*it), notify); // timer should still be running when in polling mode } // clear all pending events, also from stopped void KDirWatchPrivate::resetList( KDirWatch* /*instance*/, bool skippedToo ) { EntryMap::Iterator it = m_mapEntries.begin(); for( ; it != m_mapEntries.end(); ++it ) { Client* c = (*it).m_clients.first(); for(;c;c=(*it).m_clients.next()) if (!c->watchingStopped || skippedToo) c->pending = NoChange; } } // Return event happened on <e> // int KDirWatchPrivate::scanEntry(Entry* e) { #ifdef HAVE_FAM // we do not stat entries using FAM if (e->m_mode == FAMMode) return NoChange; #endif // Shouldn't happen: Ignore "unknown" notification method if (e->m_mode == UnknownMode) return NoChange; #ifdef HAVE_DNOTIFY if (e->m_mode == DNotifyMode) { // we know nothing has changed, no need to stat if(!e->dn_dirty) return NoChange; e->dn_dirty = false; } #endif if (e->m_mode == StatMode) { // only scan if timeout on entry timer happens; // e.g. when using 500msec global timer, a entry // with freq=5000 is only watched every 10th time e->msecLeft -= freq; if (e->msecLeft>0) return NoChange; e->msecLeft += e->freq; } struct stat stat_buf; bool exists = (stat(QFile::encodeName(e->path), &stat_buf) == 0); if (exists) { if (e->m_status == NonExistent) { e->m_ctime = stat_buf.st_ctime; e->m_status = Normal; e->m_nlink = stat_buf.st_nlink; return Created; } if ( (e->m_ctime != invalid_ctime) && ((stat_buf.st_ctime != e->m_ctime) || // (stat_buf.st_nlink != (nlink_t) e->m_nlink)) ) { (stat_buf.st_nlink != e->m_nlink)) ) { e->m_ctime = stat_buf.st_ctime; e->m_nlink = stat_buf.st_nlink; return Changed; } return NoChange; } // dir/file doesn't exist if (e->m_ctime == invalid_ctime) return NoChange; e->m_ctime = invalid_ctime; e->m_nlink = 0; e->m_status = NonExistent; return Deleted; } /* Notify all interested KDirWatch instances about a given event on an entry * and stored pending events. When watching is stopped, the event is * added to the pending events. */ void KDirWatchPrivate::emitEvent(Entry* e, int event, const QString &fileName) { QString path = e->path; if (!fileName.isEmpty()) { if (fileName[0] == '/') path = fileName; else path += "/" + fileName; } Client* c = e->m_clients.first(); for(;c;c=e->m_clients.next()) { if (c->instance==0 || c->count==0) continue; if (c->watchingStopped) { // add event to pending... if (event == Changed) c->pending |= event; else if (event == Created || event == Deleted) c->pending = event; continue; } // not stopped if (event == NoChange || event == Changed) event |= c->pending; c->pending = NoChange; if (event == NoChange) continue; if (event & Deleted) { c->instance->setDeleted(path); // emit only Deleted event... continue; } if (event & Created) { c->instance->setCreated(path); // possible emit Change event after creation } if (event & Changed) c->instance->setDirty(path); } } // Remove entries which were marked to be removed void KDirWatchPrivate::slotRemoveDelayed() { |