| /**************************************************************************** |
| ** |
| ** 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$ |
| ** GNU Lesser General Public License Usage |
| ** 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. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU General |
| ** Public License version 3.0 as published by the Free Software Foundation |
| ** and appearing in the file LICENSE.GPL included in the packaging of this |
| ** file. Please review the following information to ensure the GNU General |
| ** Public License version 3.0 requirements will be met: |
| ** http://www.gnu.org/copyleft/gpl.html. |
| ** |
| ** Other Usage |
| ** Alternatively, this file may be used in accordance with the terms and |
| ** conditions contained in a signed written agreement between you and Nokia. |
| ** |
| ** |
| ** |
| ** |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qplatformdefs.h" |
| #include "qfilesystemwatcher.h" |
| #include "qfilesystemwatcher_dnotify_p.h" |
| |
| #ifndef QT_NO_FILESYSTEMWATCHER |
| |
| #include <qsocketnotifier.h> |
| #include <qcoreapplication.h> |
| #include <qfileinfo.h> |
| #include <qtimer.h> |
| #include <qwaitcondition.h> |
| #include <qmutex.h> |
| #include <dirent.h> |
| #include <qdir.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <signal.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <time.h> |
| |
| #include "private/qcore_unix_p.h" |
| |
| #ifdef QT_LINUXBASE |
| |
| /* LSB doesn't standardize these */ |
| #define F_NOTIFY 1026 |
| #define DN_ACCESS 0x00000001 |
| #define DN_MODIFY 0x00000002 |
| #define DN_CREATE 0x00000004 |
| #define DN_DELETE 0x00000008 |
| #define DN_RENAME 0x00000010 |
| #define DN_ATTRIB 0x00000020 |
| #define DN_MULTISHOT 0x80000000 |
| |
| #endif |
| |
| QT_BEGIN_NAMESPACE |
| |
| static int qfswd_fileChanged_pipe[2]; |
| static void (*qfswd_old_sigio_handler)(int) = 0; |
| static void (*qfswd_old_sigio_action)(int, siginfo_t *, void *) = 0; |
| static void qfswd_sigio_monitor(int signum, siginfo_t *i, void *v) |
| { |
| qt_safe_write(qfswd_fileChanged_pipe[1], reinterpret_cast<char*>(&i->si_fd), sizeof(int)); |
| |
| if (qfswd_old_sigio_handler && qfswd_old_sigio_handler != SIG_IGN) |
| qfswd_old_sigio_handler(signum); |
| if (qfswd_old_sigio_action) |
| qfswd_old_sigio_action(signum, i, v); |
| } |
| |
| class QDnotifySignalThread : public QThread |
| { |
| Q_OBJECT |
| public: |
| QDnotifySignalThread(); |
| virtual ~QDnotifySignalThread(); |
| |
| void startNotify(); |
| |
| virtual void run(); |
| |
| signals: |
| void fdChanged(int); |
| |
| protected: |
| virtual bool event(QEvent *); |
| |
| private slots: |
| void readFromDnotify(); |
| |
| private: |
| QMutex mutex; |
| QWaitCondition wait; |
| bool isExecing; |
| }; |
| |
| Q_GLOBAL_STATIC(QDnotifySignalThread, dnotifySignal) |
| |
| QDnotifySignalThread::QDnotifySignalThread() |
| : isExecing(false) |
| { |
| moveToThread(this); |
| |
| qt_safe_pipe(qfswd_fileChanged_pipe, O_NONBLOCK); |
| |
| struct sigaction oldAction; |
| struct sigaction action; |
| memset(&action, 0, sizeof(action)); |
| action.sa_sigaction = qfswd_sigio_monitor; |
| action.sa_flags = SA_SIGINFO; |
| ::sigaction(SIGIO, &action, &oldAction); |
| if (!(oldAction.sa_flags & SA_SIGINFO)) |
| qfswd_old_sigio_handler = oldAction.sa_handler; |
| else |
| qfswd_old_sigio_action = oldAction.sa_sigaction; |
| } |
| |
| QDnotifySignalThread::~QDnotifySignalThread() |
| { |
| if(isRunning()) { |
| quit(); |
| QThread::wait(); |
| } |
| } |
| |
| bool QDnotifySignalThread::event(QEvent *e) |
| { |
| if(e->type() == QEvent::User) { |
| QMutexLocker locker(&mutex); |
| isExecing = true; |
| wait.wakeAll(); |
| return true; |
| } else { |
| return QThread::event(e); |
| } |
| } |
| |
| void QDnotifySignalThread::startNotify() |
| { |
| // Note: All this fancy waiting for the thread to enter its event |
| // loop is to avoid nasty messages at app shutdown when the |
| // QDnotifySignalThread singleton is deleted |
| start(); |
| mutex.lock(); |
| while(!isExecing) |
| wait.wait(&mutex); |
| mutex.unlock(); |
| } |
| |
| void QDnotifySignalThread::run() |
| { |
| QSocketNotifier sn(qfswd_fileChanged_pipe[0], QSocketNotifier::Read, this); |
| connect(&sn, SIGNAL(activated(int)), SLOT(readFromDnotify())); |
| |
| QCoreApplication::instance()->postEvent(this, new QEvent(QEvent::User)); |
| (void) exec(); |
| } |
| |
| void QDnotifySignalThread::readFromDnotify() |
| { |
| int fd; |
| int readrv = qt_safe_read(qfswd_fileChanged_pipe[0], reinterpret_cast<char*>(&fd), sizeof(int)); |
| // Only expect EAGAIN or EINTR. Other errors are assumed to be impossible. |
| if(readrv != -1) { |
| Q_ASSERT(readrv == sizeof(int)); |
| Q_UNUSED(readrv); |
| |
| if(0 == fd) |
| quit(); |
| else |
| emit fdChanged(fd); |
| } |
| } |
| |
| QDnotifyFileSystemWatcherEngine::QDnotifyFileSystemWatcherEngine() |
| { |
| QObject::connect(dnotifySignal(), SIGNAL(fdChanged(int)), |
| this, SLOT(refresh(int)), Qt::DirectConnection); |
| } |
| |
| QDnotifyFileSystemWatcherEngine::~QDnotifyFileSystemWatcherEngine() |
| { |
| QMutexLocker locker(&mutex); |
| |
| for(QHash<int, Directory>::ConstIterator iter = fdToDirectory.constBegin(); |
| iter != fdToDirectory.constEnd(); |
| ++iter) { |
| qt_safe_close(iter->fd); |
| if(iter->parentFd) |
| qt_safe_close(iter->parentFd); |
| } |
| } |
| |
| QDnotifyFileSystemWatcherEngine *QDnotifyFileSystemWatcherEngine::create() |
| { |
| return new QDnotifyFileSystemWatcherEngine(); |
| } |
| |
| void QDnotifyFileSystemWatcherEngine::run() |
| { |
| qFatal("QDnotifyFileSystemWatcherEngine thread should not be run"); |
| } |
| |
| QStringList QDnotifyFileSystemWatcherEngine::addPaths(const QStringList &paths, QStringList *files, QStringList *directories) |
| { |
| QMutexLocker locker(&mutex); |
| |
| QStringList p = paths; |
| QMutableListIterator<QString> it(p); |
| |
| while (it.hasNext()) { |
| QString path = it.next(); |
| |
| QFileInfo fi(path); |
| |
| if(!fi.exists()) { |
| continue; |
| } |
| |
| bool isDir = fi.isDir(); |
| |
| if (isDir && directories->contains(path)) { |
| continue; // Skip monitored directories |
| } else if(!isDir && files->contains(path)) { |
| continue; // Skip monitored files |
| } |
| |
| if(!isDir) |
| path = fi.canonicalPath(); |
| |
| // Locate the directory entry (creating if needed) |
| int fd = pathToFD[path]; |
| |
| if(fd == 0) { |
| |
| QT_DIR *d = QT_OPENDIR(path.toUtf8().constData()); |
| if(!d) continue; // Could not open directory |
| QT_DIR *parent = 0; |
| |
| QDir parentDir(path); |
| if(!parentDir.isRoot()) { |
| parentDir.cdUp(); |
| parent = QT_OPENDIR(parentDir.path().toUtf8().constData()); |
| if(!parent) { |
| QT_CLOSEDIR(d); |
| continue; |
| } |
| } |
| |
| fd = qt_safe_dup(::dirfd(d)); |
| int parentFd = parent ? qt_safe_dup(::dirfd(parent)) : 0; |
| |
| QT_CLOSEDIR(d); |
| if(parent) QT_CLOSEDIR(parent); |
| |
| Q_ASSERT(fd); |
| if(::fcntl(fd, F_SETSIG, SIGIO) || |
| ::fcntl(fd, F_NOTIFY, DN_MODIFY | DN_CREATE | DN_DELETE | |
| DN_RENAME | DN_ATTRIB | DN_MULTISHOT) || |
| (parent && ::fcntl(parentFd, F_SETSIG, SIGIO)) || |
| (parent && ::fcntl(parentFd, F_NOTIFY, DN_DELETE | DN_RENAME | |
| DN_MULTISHOT))) { |
| continue; // Could not set appropriate flags |
| } |
| |
| Directory dir; |
| dir.path = path; |
| dir.fd = fd; |
| dir.parentFd = parentFd; |
| |
| fdToDirectory.insert(fd, dir); |
| pathToFD.insert(path, fd); |
| if(parentFd) |
| parentToFD.insert(parentFd, fd); |
| } |
| |
| Directory &directory = fdToDirectory[fd]; |
| |
| if(isDir) { |
| directory.isMonitored = true; |
| } else { |
| Directory::File file; |
| file.path = fi.filePath(); |
| file.lastWrite = fi.lastModified(); |
| directory.files.append(file); |
| pathToFD.insert(fi.filePath(), fd); |
| } |
| |
| it.remove(); |
| |
| if(isDir) { |
| directories->append(path); |
| } else { |
| files->append(fi.filePath()); |
| } |
| } |
| |
| dnotifySignal()->startNotify(); |
| |
| return p; |
| } |
| |
| QStringList QDnotifyFileSystemWatcherEngine::removePaths(const QStringList &paths, QStringList *files, QStringList *directories) |
| { |
| QMutexLocker locker(&mutex); |
| |
| QStringList p = paths; |
| QMutableListIterator<QString> it(p); |
| while (it.hasNext()) { |
| |
| QString path = it.next(); |
| int fd = pathToFD.take(path); |
| |
| if(!fd) |
| continue; |
| |
| Directory &directory = fdToDirectory[fd]; |
| bool isDir = false; |
| if(directory.path == path) { |
| isDir = true; |
| directory.isMonitored = false; |
| } else { |
| for(int ii = 0; ii < directory.files.count(); ++ii) { |
| if(directory.files.at(ii).path == path) { |
| directory.files.removeAt(ii); |
| break; |
| } |
| } |
| } |
| |
| if(!directory.isMonitored && directory.files.isEmpty()) { |
| // No longer needed |
| qt_safe_close(directory.fd); |
| pathToFD.remove(directory.path); |
| fdToDirectory.remove(fd); |
| } |
| |
| if(isDir) { |
| directories->removeAll(path); |
| } else { |
| files->removeAll(path); |
| } |
| |
| it.remove(); |
| } |
| |
| return p; |
| } |
| |
| void QDnotifyFileSystemWatcherEngine::refresh(int fd) |
| { |
| QMutexLocker locker(&mutex); |
| |
| bool wasParent = false; |
| QHash<int, Directory>::Iterator iter = fdToDirectory.find(fd); |
| if(iter == fdToDirectory.end()) { |
| QHash<int, int>::Iterator pIter = parentToFD.find(fd); |
| if(pIter == parentToFD.end()) |
| return; |
| |
| iter = fdToDirectory.find(*pIter); |
| if (iter == fdToDirectory.end()) |
| return; |
| wasParent = true; |
| } |
| |
| Directory &directory = *iter; |
| |
| if(!wasParent) { |
| for(int ii = 0; ii < directory.files.count(); ++ii) { |
| Directory::File &file = directory.files[ii]; |
| if(file.updateInfo()) { |
| // Emit signal |
| QString filePath = file.path; |
| bool removed = !QFileInfo(filePath).exists(); |
| |
| if(removed) { |
| directory.files.removeAt(ii); |
| --ii; |
| } |
| |
| emit fileChanged(filePath, removed); |
| } |
| } |
| } |
| |
| if(directory.isMonitored) { |
| // Emit signal |
| bool removed = !QFileInfo(directory.path).exists(); |
| QString path = directory.path; |
| |
| if(removed) |
| directory.isMonitored = false; |
| |
| emit directoryChanged(path, removed); |
| } |
| |
| if(!directory.isMonitored && directory.files.isEmpty()) { |
| qt_safe_close(directory.fd); |
| if(directory.parentFd) { |
| qt_safe_close(directory.parentFd); |
| parentToFD.remove(directory.parentFd); |
| } |
| fdToDirectory.erase(iter); |
| } |
| } |
| |
| void QDnotifyFileSystemWatcherEngine::stop() |
| { |
| } |
| |
| bool QDnotifyFileSystemWatcherEngine::Directory::File::updateInfo() |
| { |
| QFileInfo fi(path); |
| QDateTime nLastWrite = fi.lastModified(); |
| uint nOwnerId = fi.ownerId(); |
| uint nGroupId = fi.groupId(); |
| QFile::Permissions nPermissions = fi.permissions(); |
| |
| if(nLastWrite != lastWrite || |
| nOwnerId != ownerId || |
| nGroupId != groupId || |
| nPermissions != permissions) { |
| ownerId = nOwnerId; |
| groupId = nGroupId; |
| permissions = nPermissions; |
| lastWrite = nLastWrite; |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| QT_END_NAMESPACE |
| |
| #include "qfilesystemwatcher_dnotify.moc" |
| |
| #endif // QT_NO_FILESYSTEMWATCHER |