Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qfilesystemwatcher_kqueue.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include <qplatformdefs.h>
6
9#include "private/qcore_unix_p.h"
10
11#include <qdebug.h>
12#include <qfile.h>
13#include <qscopeguard.h>
14#include <qsocketnotifier.h>
15#include <qvarlengtharray.h>
16
17#include <sys/types.h>
18#include <sys/event.h>
19#include <sys/stat.h>
20#include <sys/time.h>
21#include <fcntl.h>
22
23QT_BEGIN_NAMESPACE
24
25// #define KEVENT_DEBUG
26#ifdef KEVENT_DEBUG
27# define DEBUG qDebug
28#else
29# define DEBUG if(false)qDebug
30#endif
31
32QKqueueFileSystemWatcherEngine *QKqueueFileSystemWatcherEngine::create(QObject *parent)
33{
34 int kqfd = kqueue();
35 if (kqfd == -1)
36 return 0;
37 return new QKqueueFileSystemWatcherEngine(kqfd, parent);
38}
39
40QKqueueFileSystemWatcherEngine::QKqueueFileSystemWatcherEngine(int kqfd, QObject *parent)
41 : QFileSystemWatcherEngine(parent),
42 kqfd(kqfd),
43 notifier(kqfd, QSocketNotifier::Read, this)
44{
45 connect(&notifier, SIGNAL(activated(QSocketDescriptor)), SLOT(readFromKqueue()));
46
47 fcntl(kqfd, F_SETFD, FD_CLOEXEC);
48}
49
50QKqueueFileSystemWatcherEngine::~QKqueueFileSystemWatcherEngine()
51{
52 notifier.setEnabled(false);
53 close(kqfd);
54
55 for (int id : std::as_const(pathToID))
56 ::close(id < 0 ? -id : id);
57}
58
59QStringList QKqueueFileSystemWatcherEngine::addPaths(const QStringList &paths,
60 QStringList *files,
61 QStringList *directories)
62{
63 QStringList unhandled;
64 for (const QString &path : paths) {
65 auto sg = qScopeGuard([&]{unhandled.push_back(path);});
66 int fd;
67#if defined(O_EVTONLY)
68 fd = qt_safe_open(QFile::encodeName(path), O_EVTONLY);
69#else
70 fd = qt_safe_open(QFile::encodeName(path), O_RDONLY);
71#endif
72 if (fd == -1) {
73 perror("QKqueueFileSystemWatcherEngine::addPaths: open");
74 continue;
75 }
76 if (fd >= (int)FD_SETSIZE / 2 && fd < (int)FD_SETSIZE) {
77 int fddup = qt_safe_dup(fd, FD_SETSIZE);
78 if (fddup != -1) {
79 ::close(fd);
80 fd = fddup;
81 }
82 }
83
84 QT_STATBUF st;
85 if (QT_FSTAT(fd, &st) == -1) {
86 perror("QKqueueFileSystemWatcherEngine::addPaths: fstat");
87 ::close(fd);
88 continue;
89 }
90 int id = (S_ISDIR(st.st_mode)) ? -fd : fd;
91 if (id < 0) {
92 if (directories->contains(path)) {
93 ::close(fd);
94 continue;
95 }
96 } else {
97 if (files->contains(path)) {
98 ::close(fd);
99 continue;
100 }
101 }
102
103 struct kevent kev;
104 EV_SET(&kev,
105 fd,
106 EVFILT_VNODE,
107 EV_ADD | EV_ENABLE | EV_CLEAR,
108 NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_RENAME | NOTE_REVOKE,
109 0,
110 0);
111 if (kevent(kqfd, &kev, 1, 0, 0, 0) == -1) {
112 perror("QKqueueFileSystemWatcherEngine::addPaths: kevent");
113 ::close(fd);
114 continue;
115 }
116
117 sg.dismiss();
118
119 if (id < 0) {
120 DEBUG() << "QKqueueFileSystemWatcherEngine: added directory path" << path;
121 directories->append(path);
122 } else {
123 DEBUG() << "QKqueueFileSystemWatcherEngine: added file path" << path;
124 files->append(path);
125 }
126
127 pathToID.insert(path, id);
128 idToPath.insert(id, path);
129 }
130
131 return unhandled;
132}
133
134QStringList QKqueueFileSystemWatcherEngine::removePaths(const QStringList &paths,
135 QStringList *files,
136 QStringList *directories)
137{
138 if (pathToID.isEmpty())
139 return paths;
140
141 QStringList unhandled;
142 for (const QString &path : paths) {
143 auto sg = qScopeGuard([&]{unhandled.push_back(path);});
144 int id = pathToID.take(path);
145 QString x = idToPath.take(id);
146 if (x.isEmpty() || x != path)
147 continue;
148
149 ::close(id < 0 ? -id : id);
150
151 sg.dismiss();
152
153 if (id < 0)
154 directories->removeAll(path);
155 else
156 files->removeAll(path);
157 }
158
159 return unhandled;
160}
161
162void QKqueueFileSystemWatcherEngine::readFromKqueue()
163{
164 forever {
165 DEBUG() << "QKqueueFileSystemWatcherEngine: polling for changes";
166 int r;
167 struct kevent kev;
168 struct timespec ts = { 0, 0 }; // 0 ts, because we want to poll
169 QT_EINTR_LOOP(r, kevent(kqfd, 0, 0, &kev, 1, &ts));
170 if (r < 0) {
171 perror("QKqueueFileSystemWatcherEngine: error during kevent wait");
172 return;
173 } else if (r == 0) {
174 // polling returned no events, so stop
175 break;
176 } else {
177 int fd = kev.ident;
178
179 DEBUG() << "QKqueueFileSystemWatcherEngine: processing kevent" << kev.ident << kev.filter;
180
181 int id = fd;
182 QString path = idToPath.value(id);
183 if (path.isEmpty()) {
184 // perhaps a directory?
185 id = -id;
186 path = idToPath.value(id);
187 if (path.isEmpty()) {
188 DEBUG() << "QKqueueFileSystemWatcherEngine: received a kevent for a file we're not watching";
189 continue;
190 }
191 }
192 if (kev.filter != EVFILT_VNODE) {
193 DEBUG() << "QKqueueFileSystemWatcherEngine: received a kevent with the wrong filter";
194 continue;
195 }
196
197 if ((kev.fflags & (NOTE_DELETE | NOTE_REVOKE | NOTE_RENAME)) != 0) {
198 DEBUG() << path << "removed, removing watch also";
199
200 pathToID.remove(path);
201 idToPath.remove(id);
202 ::close(fd);
203
204 if (id < 0)
205 emit directoryChanged(path, true);
206 else
207 emit fileChanged(path, true);
208 } else {
209 DEBUG() << path << "changed";
210
211 if (id < 0)
212 emit directoryChanged(path, false);
213 else
214 emit fileChanged(path, false);
215 }
216 }
217
218 }
219}
220
221QT_END_NAMESPACE
222
223#include "moc_qfilesystemwatcher_kqueue_p.cpp"