/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: iaom * */ #include "file-system-watcher-private.h" #include #include #include #include #include #include #include #include #include "ukui-search-qdbus.h" #include "file-utils.h" #include "dir-watcher.h" using namespace UkuiSearch; FileSystemWatcherPrivate::FileSystemWatcherPrivate(FileSystemWatcher *parent) : q(parent) { qDebug() << "setInotifyMaxUserWatches start"; UkuiSearchQDBus usQDBus; usQDBus.setInotifyMaxUserWatches(); qDebug() << "setInotifyMaxUserWatches end"; init(); } FileSystemWatcherPrivate::~FileSystemWatcherPrivate() { close(m_inotifyFd); if(m_notifier) { delete m_notifier; m_notifier = nullptr; } } void FileSystemWatcherPrivate::addWatch(const QString &path) { int wd = inotify_add_watch(m_inotifyFd, path.toStdString().c_str(), m_watchEvents | m_watchFlags); if(wd > 0) { m_watchPathHash[wd] = path; } else { qWarning() << "AddWatch error:" << path << strerror(errno); if (errno == ENOSPC) { qWarning() << "User limit reached. Count: " << m_watchPathHash.count(); } } } void FileSystemWatcherPrivate::addWatchWithBlackList(const QStringList &pathList, const QStringList &blackList) { QQueue bfs; QStringList tmpPathList = pathList; for(QString blackPath : blackList) { for(QString path : pathList) { if(FileUtils::isOrUnder(path, blackPath)) { tmpPathList.removeOne(path); } } } for(QString path : tmpPathList) { if(!m_watchedRootPaths.contains(path)) { addWatch(path); bfs.enqueue(path); m_watchedRootPaths.append(path); } } if(!m_recursive) { return; } QFileInfoList list; QDir dir; QStringList tmpList = blackList; dir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); while(!bfs.empty()) { dir.setPath(bfs.dequeue()); list = dir.entryInfoList(); for(auto i : list) { bool isBlocked = false; for(QString path : tmpList) { if(i.absoluteFilePath() == path) { isBlocked = true; tmpList.removeOne(path); break; } } if(isBlocked) continue; if(!(i.isSymLink())) { addWatch(i.absoluteFilePath()); bfs.enqueue(i.absoluteFilePath()); } } } } QStringList FileSystemWatcherPrivate::removeWatch(const QString &path) { QStringList paths; QHash::iterator iter = m_watchPathHash.begin(); while(iter != m_watchPathHash.end()) { if(iter.value().length() >= path.length() && FileUtils::isOrUnder(iter.value(), path)) { paths.append(iter.value()); inotify_rm_watch(m_inotifyFd, iter.key()); iter = m_watchPathHash.erase(iter); } else { ++iter; } } m_watchPathHash.squeeze(); return paths; } QString FileSystemWatcherPrivate::removeWatch(int wd) { inotify_rm_watch(m_inotifyFd, wd); return m_watchPathHash.take(wd); } void FileSystemWatcherPrivate::clearAll() { if(m_inotifyFd > 0) { close(m_inotifyFd); m_inotifyFd = -1; } if(m_notifier) { delete m_notifier; m_notifier = nullptr; } m_watchPathHash.clear(); m_watchedRootPaths.clear(); init(); } void FileSystemWatcherPrivate::init() { if(m_inotifyFd < 0) { m_inotifyFd = inotify_init(); if (m_inotifyFd > 0) { qDebug()<<"Inotify init success!"; } else { qWarning() << "Inotify init fail! Now try add inotify_user_instances."; UkuiSearchQDBus usQDBus; usQDBus.addInotifyUserInstances(128); m_inotifyFd = inotify_init(); if (m_inotifyFd > 0) { qDebug()<<"Inotify init success!"; } else { printf("errno=%d\n",errno); printf("Mesg:%s\n",strerror(errno)); Q_ASSERT_X(0, "InotifyWatch", "Failed to initialize inotify"); return; } } fcntl(m_inotifyFd, F_SETFD, FD_CLOEXEC); m_notifier = new QSocketNotifier(m_inotifyFd, QSocketNotifier::Read); QObject::connect(m_notifier, &QSocketNotifier::activated, q, &FileSystemWatcher::eventProcess); } } FileSystemWatcher::FileSystemWatcher(bool recursive, WatchEvents events, WatchFlags flags, QObject *parent) : QObject(parent) , d(new FileSystemWatcherPrivate(this)) { d->m_watchEvents = events; d->m_watchFlags = flags; d->m_recursive = recursive; connect(DirWatcher::getDirWatcher(), &DirWatcher::mountAdded, this, [ & ] (const QString& mountPoint) { for (const auto &path: d->m_failedWatchPaths) { if (FileUtils::isOrUnder(path, mountPoint)) { qDebug() << "the path that fail to watch has been mounted: " << path; d->m_failedWatchPaths.removeAll(path); for(const QString &changedPath : traverse(path)) { QFileInfo info(changedPath); if (info.isReadable() && info.isExecutable()) { Q_EMIT accessibleChanged(changedPath, info.isDir(), true); } } break; } } }); } FileSystemWatcher::~FileSystemWatcher() { if(d) { delete d; d = nullptr; } } void FileSystemWatcher::addWatch(const QStringList &pathList) { d->addWatchWithBlackList(pathList, QStringList()); } void FileSystemWatcher::addWatch(const QString &path) { d->addWatchWithBlackList(QStringList(path), QStringList()); } void FileSystemWatcher::addWatchWithBlackList(const QStringList &pathList, const QStringList &blackList) { d->addWatchWithBlackList(pathList, blackList); } QStringList FileSystemWatcher::removeWatch(const QString &path) { for(QString watchedPath : d->m_watchedRootPaths) { if(FileUtils::isOrUnder(watchedPath, path)) { d->m_watchedRootPaths.removeAll(watchedPath); } } return d->removeWatch(path); } void FileSystemWatcher::clearAll() { d->clearAll(); } void FileSystemWatcher::eventProcess(int socket) { // qDebug() << "-----begin event process-----"; int avail; if (ioctl(socket, FIONREAD, &avail) == EINVAL) { qWarning() << "Did not receive an entire inotify event."; return; } char* buf = (char*)malloc(avail); memset(buf, 0x00, avail); const ssize_t len = read(socket, buf, avail); if(len != avail) { qWarning()<<"read event error"; } int i = 0; while (i < len) { const struct inotify_event* event = (struct inotify_event*)&buf[i]; if(event->name[0] == '.') { i += sizeof(struct inotify_event) + event->len; continue; } if (event->wd < 0 && (event->mask & EventQueueOverflow)) { qWarning() << "Inotify Event queued overflowed"; free(buf); return; } // qDebug() << "event mask:" << event->mask // << "isDir:" << (event->mask & IN_ISDIR) // << "event->wd:" << event->wd // << "event->name" << QString(QByteArray::fromRawData(event->name, qstrnlen(event->name, event->len))) // << "event->len" << event->len // << "event->cookie" << event->cookie // << "path:" << d->m_watchPathHash.value(event->wd); QString path; if (event->mask & (EventDeleteSelf | EventMoveSelf | EventUnmount)) { path = d->m_watchPathHash.value(event->wd); if(path.isEmpty()) { i += sizeof(struct inotify_event) + event->len; continue; } } else { QByteArray name = QByteArray::fromRawData(event->name, qstrnlen(event->name, event->len)); path = d->m_watchPathHash[event->wd]; if(name.isEmpty() || path.isEmpty()) { i += sizeof(struct inotify_event) + event->len; continue; } path.append("/").append(name); } if(event->mask & EventCreate) { // qDebug() << path << "--EventCreate"; Q_EMIT created(path, event->mask & IN_ISDIR); if(event->mask & IN_ISDIR) { for(const QString &createdPath : traverse(path)) { Q_EMIT created(createdPath, QFileInfo(createdPath).isDir()); } } } if (event->mask & EventDeleteSelf) { // qDebug() << path << "--EventDeleteSelf"; if(d->m_watchedRootPaths.contains(path)) { // qDebug() << "EventDeleteSelf send" << path; //All folders under this one should be deleted. for(const QString &removedPath : d->removeWatch(path)) { Q_EMIT deleted(removedPath, true); } d->m_watchedRootPaths.removeAll(path); } } if (event->mask & EventDelete) { // qDebug() << path << "--EventDelete"; if (event->mask & IN_ISDIR) { for(const QString &removedPath : d->removeWatch(path)) { Q_EMIT deleted(removedPath, true); } } else { Q_EMIT deleted(path, false); } } if (event->mask & EventModify) { // qDebug() << path << "--EventModify"; if(!(event->mask & IN_ISDIR)) { Q_EMIT modified(path); } } if (event->mask & EventMoveSelf) { // qDebug() << path << "--EventMoveSelf"; if(d->m_watchedRootPaths.contains(path)) { for(const QString &removedPath : d->removeWatch(path)) { Q_EMIT moved(removedPath, true); } d->m_watchedRootPaths.removeAll(path); } } if (event->mask & EventMoveFrom) { // qDebug() << path << "--EventMoveFrom"; if (event->mask & IN_ISDIR) { for(const QString &removedPath : d->removeWatch(path)) { Q_EMIT moved(removedPath, true); } } else { Q_EMIT moved(path, false); } } if (event->mask & EventMoveTo) { // qDebug() << path << "--EventMoveTo"; Q_EMIT moveTo(path, event->mask & IN_ISDIR); if (event->mask & IN_ISDIR) { for(const QString &createdPath : traverse(path)) { Q_EMIT moveTo(createdPath, QFileInfo(createdPath).isDir()); } } } if (event->mask & EventOpen) { // qDebug() << path << "--EventOpen"; Q_EMIT opened(path); } if (event->mask & EventUnmount) { // qDebug() << path << "--EventUnmount"; // This is present because a unmount event is sent by inotify after unmounting, by // which time the watches have already been removed. if (path != "/") { Q_EMIT unmounted(path, event->mask & IN_ISDIR); } d->m_watchedRootPaths.removeAll(path); } if (event->mask & EventAttributeChange) { // qDebug() << path << "--EventAttributeChange"; Q_EMIT attributeChanged(path); if (event->mask & IN_ISDIR) { QFileInfo info(path); if(!info.isReadable() || !info.isExecutable()) { for(const QString &removedPath : d->removeWatch(path)) { Q_EMIT accessibleChanged(removedPath, true, false); } } else { if (!d->m_watchPathHash.values().contains(path)) { for(const QString &createdPath : traverse(path)) { Q_EMIT accessibleChanged(createdPath, QFileInfo(createdPath).isDir(), true); } } } } else { Q_EMIT accessibleChanged(path, false, QFileInfo(path).isReadable()); } } if (event->mask & EventAccess) { // qDebug() << path << "--EventAccess"; Q_EMIT accessed(path); } if (event->mask & EventCloseWrite) { // qDebug() << path << "--EventCloseWrite"; Q_EMIT closedWrite(path); } if (event->mask & EventCloseRead) { // qDebug() << path << "--EventCloseRead"; Q_EMIT closedRead(path); } if (event->mask & EventIgnored) { // qDebug() << path << "--EventIgnored"; } i += sizeof(struct inotify_event) + event->len; } if (len < 0) { qWarning() << "Failed to read event."; } free(buf); } QStringList FileSystemWatcher::traverse(const QString &path) { QStringList paths; QFileInfo info(path); if (info.isReadable() && info.isExecutable()) { d->addWatch(path); } else { qWarning() << "Fail to watch" << path << ".Permission Dined"; d->m_failedWatchPaths.append(path); } if(!d->m_recursive || QFileInfo(path).isSymLink()) { return paths; } QQueue queue; queue.enqueue(path); QFileInfoList list; QDir dir; dir.setFilter(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot); dir.setSorting(QDir::DirsFirst); while(!queue.empty()) { dir.setPath(queue.dequeue()); list = dir.entryInfoList(); for(auto i : list) { if(i.isDir() && !(i.isSymLink())) { if (i.isReadable() && i.isExecutable()) { queue.enqueue(i.absoluteFilePath()); d->addWatch(i.absoluteFilePath()); } else { qWarning() << "Fail to watch" << i.absoluteFilePath(); d->m_failedWatchPaths.append(i.absoluteFilePath()); } } paths.append(i.absoluteFilePath()); } } return paths; }