summaryrefslogtreecommitdiffstats
path: root/src/corelib/io/qfilesystemwatcher_kqueue.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/io/qfilesystemwatcher_kqueue.cpp')
-rw-r--r--src/corelib/io/qfilesystemwatcher_kqueue.cpp334
1 files changed, 334 insertions, 0 deletions
diff --git a/src/corelib/io/qfilesystemwatcher_kqueue.cpp b/src/corelib/io/qfilesystemwatcher_kqueue.cpp
new file mode 100644
index 0000000000..6c36a82fd1
--- /dev/null
+++ b/src/corelib/io/qfilesystemwatcher_kqueue.cpp
@@ -0,0 +1,334 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtCore module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <qplatformdefs.h>
+
+#include "qfilesystemwatcher.h"
+#include "qfilesystemwatcher_kqueue_p.h"
+#include "private/qcore_unix_p.h"
+
+#ifndef QT_NO_FILESYSTEMWATCHER
+
+#include <qdebug.h>
+#include <qfile.h>
+#include <qsocketnotifier.h>
+#include <qvarlengtharray.h>
+
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <fcntl.h>
+
+QT_BEGIN_NAMESPACE
+
+// #define KEVENT_DEBUG
+#ifdef KEVENT_DEBUG
+# define DEBUG qDebug
+#else
+# define DEBUG if(false)qDebug
+#endif
+
+QKqueueFileSystemWatcherEngine *QKqueueFileSystemWatcherEngine::create()
+{
+ int kqfd = kqueue();
+ if (kqfd == -1)
+ return 0;
+ return new QKqueueFileSystemWatcherEngine(kqfd);
+}
+
+QKqueueFileSystemWatcherEngine::QKqueueFileSystemWatcherEngine(int kqfd)
+ : kqfd(kqfd)
+{
+ fcntl(kqfd, F_SETFD, FD_CLOEXEC);
+
+ if (pipe(kqpipe) == -1) {
+ perror("QKqueueFileSystemWatcherEngine: cannot create pipe");
+ kqpipe[0] = kqpipe[1] = -1;
+ return;
+ }
+ fcntl(kqpipe[0], F_SETFD, FD_CLOEXEC);
+ fcntl(kqpipe[1], F_SETFD, FD_CLOEXEC);
+
+ struct kevent kev;
+ EV_SET(&kev,
+ kqpipe[0],
+ EVFILT_READ,
+ EV_ADD | EV_ENABLE,
+ 0,
+ 0,
+ 0);
+ if (kevent(kqfd, &kev, 1, 0, 0, 0) == -1) {
+ perror("QKqueueFileSystemWatcherEngine: cannot watch pipe, kevent returned");
+ return;
+ }
+}
+
+QKqueueFileSystemWatcherEngine::~QKqueueFileSystemWatcherEngine()
+{
+ stop();
+ wait();
+
+ close(kqfd);
+ close(kqpipe[0]);
+ close(kqpipe[1]);
+
+ foreach (int id, pathToID)
+ ::close(id < 0 ? -id : id);
+}
+
+QStringList QKqueueFileSystemWatcherEngine::addPaths(const QStringList &paths,
+ QStringList *files,
+ QStringList *directories)
+{
+ QStringList p = paths;
+ {
+ QMutexLocker locker(&mutex);
+
+ QMutableListIterator<QString> it(p);
+ while (it.hasNext()) {
+ QString path = it.next();
+ int fd;
+#if defined(O_EVTONLY)
+ fd = qt_safe_open(QFile::encodeName(path), O_EVTONLY);
+#else
+ fd = qt_safe_open(QFile::encodeName(path), O_RDONLY);
+#endif
+ if (fd == -1) {
+ perror("QKqueueFileSystemWatcherEngine::addPaths: open");
+ continue;
+ }
+ if (fd >= (int)FD_SETSIZE / 2 && fd < (int)FD_SETSIZE) {
+ int fddup = fcntl(fd, F_DUPFD, FD_SETSIZE);
+ if (fddup != -1) {
+ ::close(fd);
+ fd = fddup;
+ }
+ }
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+
+ QT_STATBUF st;
+ if (QT_FSTAT(fd, &st) == -1) {
+ perror("QKqueueFileSystemWatcherEngine::addPaths: fstat");
+ ::close(fd);
+ continue;
+ }
+ int id = (S_ISDIR(st.st_mode)) ? -fd : fd;
+ if (id < 0) {
+ if (directories->contains(path)) {
+ ::close(fd);
+ continue;
+ }
+ } else {
+ if (files->contains(path)) {
+ ::close(fd);
+ continue;
+ }
+ }
+
+ struct kevent kev;
+ EV_SET(&kev,
+ fd,
+ EVFILT_VNODE,
+ EV_ADD | EV_ENABLE | EV_CLEAR,
+ NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_RENAME | NOTE_REVOKE,
+ 0,
+ 0);
+ if (kevent(kqfd, &kev, 1, 0, 0, 0) == -1) {
+ perror("QKqueueFileSystemWatcherEngine::addPaths: kevent");
+ ::close(fd);
+ continue;
+ }
+
+ it.remove();
+ if (id < 0) {
+ DEBUG() << "QKqueueFileSystemWatcherEngine: added directory path" << path;
+ directories->append(path);
+ } else {
+ DEBUG() << "QKqueueFileSystemWatcherEngine: added file path" << path;
+ files->append(path);
+ }
+
+ pathToID.insert(path, id);
+ idToPath.insert(id, path);
+ }
+ }
+
+ if (!isRunning())
+ start();
+ else
+ write(kqpipe[1], "@", 1);
+
+ return p;
+}
+
+QStringList QKqueueFileSystemWatcherEngine::removePaths(const QStringList &paths,
+ QStringList *files,
+ QStringList *directories)
+{
+ bool isEmpty;
+ QStringList p = paths;
+ {
+ QMutexLocker locker(&mutex);
+ if (pathToID.isEmpty())
+ return p;
+
+ QMutableListIterator<QString> it(p);
+ while (it.hasNext()) {
+ QString path = it.next();
+ int id = pathToID.take(path);
+ QString x = idToPath.take(id);
+ if (x.isEmpty() || x != path)
+ continue;
+
+ ::close(id < 0 ? -id : id);
+
+ it.remove();
+ if (id < 0)
+ directories->removeAll(path);
+ else
+ files->removeAll(path);
+ }
+ isEmpty = pathToID.isEmpty();
+ }
+
+ if (isEmpty) {
+ stop();
+ wait();
+ } else {
+ write(kqpipe[1], "@", 1);
+ }
+
+ return p;
+}
+
+void QKqueueFileSystemWatcherEngine::stop()
+{
+ write(kqpipe[1], "q", 1);
+}
+
+void QKqueueFileSystemWatcherEngine::run()
+{
+ forever {
+ int r;
+ struct kevent kev;
+ DEBUG() << "QKqueueFileSystemWatcherEngine: waiting for kevents...";
+ EINTR_LOOP(r, kevent(kqfd, 0, 0, &kev, 1, 0));
+ if (r < 0) {
+ perror("QKqueueFileSystemWatcherEngine: error during kevent wait");
+ return;
+ } else {
+ int fd = kev.ident;
+
+ DEBUG() << "QKqueueFileSystemWatcherEngine: processing kevent" << kev.ident << kev.filter;
+ if (fd == kqpipe[0]) {
+ // read all pending data from the pipe
+ QByteArray ba;
+ ba.resize(kev.data);
+ if (read(kqpipe[0], ba.data(), ba.size()) != ba.size()) {
+ perror("QKqueueFileSystemWatcherEngine: error reading from pipe");
+ return;
+ }
+ // read the command from the buffer (but break and return on 'q')
+ char cmd = 0;
+ for (int i = 0; i < ba.size(); ++i) {
+ cmd = ba.constData()[i];
+ if (cmd == 'q')
+ break;
+ }
+ // handle the command
+ switch (cmd) {
+ case 'q':
+ DEBUG() << "QKqueueFileSystemWatcherEngine: thread received 'q', exiting...";
+ return;
+ case '@':
+ DEBUG() << "QKqueueFileSystemWatcherEngine: thread received '@', continuing...";
+ break;
+ default:
+ DEBUG() << "QKqueueFileSystemWatcherEngine: thread received unknow message" << cmd;
+ break;
+ }
+ } else {
+ QMutexLocker locker(&mutex);
+
+ int id = fd;
+ QString path = idToPath.value(id);
+ if (path.isEmpty()) {
+ // perhaps a directory?
+ id = -id;
+ path = idToPath.value(id);
+ if (path.isEmpty()) {
+ DEBUG() << "QKqueueFileSystemWatcherEngine: received a kevent for a file we're not watching";
+ continue;
+ }
+ }
+ if (kev.filter != EVFILT_VNODE) {
+ DEBUG() << "QKqueueFileSystemWatcherEngine: received a kevent with the wrong filter";
+ continue;
+ }
+
+ if ((kev.fflags & (NOTE_DELETE | NOTE_REVOKE | NOTE_RENAME)) != 0) {
+ DEBUG() << path << "removed, removing watch also";
+
+ pathToID.remove(path);
+ idToPath.remove(id);
+ ::close(fd);
+
+ if (id < 0)
+ emit directoryChanged(path, true);
+ else
+ emit fileChanged(path, true);
+ } else {
+ DEBUG() << path << "changed";
+
+ if (id < 0)
+ emit directoryChanged(path, false);
+ else
+ emit fileChanged(path, false);
+ }
+ }
+ }
+ }
+}
+
+#endif //QT_NO_FILESYSTEMWATCHER
+
+QT_END_NAMESPACE