/* * 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 "ukui-search-qdbus.h" #include "file-utils.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::traverse(QStringList pathList) { QQueue queue; for(QString path : pathList) { addWatch(path); queue.enqueue(path); } if(!m_recursive) { return; } QFileInfoList list; QDir dir; dir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); while(!queue.empty()) { dir.setPath(queue.dequeue()); list = dir.entryInfoList(); for(auto i : list) { if(!(i.isSymLink())) { queue.enqueue(i.absoluteFilePath()); addWatch(i.absoluteFilePath()); } } } } 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::addWatch(const QStringList &pathList) { traverse(pathList); } 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) { addWatch(path); bfs.enqueue(path); } 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; for(int wd : m_watchPathHash.keys()) { QString tmpPath = m_watchPathHash.value(wd); if(tmpPath.length() >= path.length()) { if(FileUtils::isOrUnder(tmpPath, path)) { //fix me:This function can be slow (O(n)) paths.append(removeWatch(m_watchPathHash.key(path))); } } } 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(); 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; } FileSystemWatcher::~FileSystemWatcher() { if(d) { delete d; d = nullptr; } } void FileSystemWatcher::addWatch(const QStringList &pathList) { d->addWatch(pathList); } void FileSystemWatcher::addWatch(const QString &path) { d->addWatch(QStringList(path)); } void FileSystemWatcher::addWatchWithBlackList(const QStringList &pathList, const QStringList &blackList) { d->addWatchWithBlackList(pathList, blackList); } QStringList FileSystemWatcher::removeWatch(const QString &path) { 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; QString path; if (event->mask & (EventDeleteSelf | EventMoveSelf)) { path = d->m_watchPathHash.value(event->wd); } else { path = d->m_watchPathHash[event->wd] + '/' + event->name; } if(event->mask & EventCreate) { // qDebug() << path << "--EventCreate"; Q_EMIT created(path, event->mask & IN_ISDIR); if(event->mask & IN_ISDIR && d->m_recursive) { if(!QFileInfo(path).isSymLink()){ addWatch(QStringList(path)); } } } if (event->mask & EventDeleteSelf) { // qDebug() << path << "--EventDeleteSelf"; if(event->mask & IN_ISDIR) { d->removeWatch(event->wd); } Q_EMIT deleted(path, event->mask & IN_ISDIR); } if (event->mask & EventDelete) { // qDebug() << path << "--EventDelete"; // we watch all folders recursively. Thus, folder removing is reported in DeleteSelf. if (!(event->mask & IN_ISDIR)) { Q_EMIT deleted(path, false); } } if (event->mask & EventModify) { // qDebug() << path << "--EventModify"; Q_EMIT modified(path); } if (event->mask & EventMoveSelf) { //Problematic if the parent is not watched, otherwise // handled by MoveFrom/MoveTo from the parent // qDebug() << path << "--EventMoveSelf"; } if (event->mask & EventMoveFrom) { // qDebug() << path << "--EventMoveFrom"; Q_EMIT moved(path, event->mask & IN_ISDIR); } if (event->mask & EventMoveTo) { // qDebug() << path << "--EventMoveTo"; Q_EMIT created(path, event->mask & IN_ISDIR); if (event->mask & IN_ISDIR && d->m_recursive) { if(!QFileInfo(path).isSymLink()){ addWatch(QStringList(path)); } } } if (event->mask & EventOpen) { // qDebug() << path << "--EventOpen"; Q_EMIT opened(path); } if (event->mask & EventUnmount) { // qDebug() << path << "--EventUnmount"; if (event->mask & IN_ISDIR) { d->removeWatch(event->wd); } // 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); } } if (event->mask & EventAttributeChange) { // qDebug() << path << "--EventAttributeChange"; Q_EMIT attributeChanged(path); } 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); }