1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtCore module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39
40 #include <qplatformdefs.h>
41
42 #include "qfilesystemwatcher.h"
43 #include "qfilesystemwatcher_kqueue_p.h"
44 #include "private/qcore_unix_p.h"
45
46 #include <qdebug.h>
47 #include <qfile.h>
48 #include <qscopeguard.h>
49 #include <qsocketnotifier.h>
50 #include <qvarlengtharray.h>
51
52 #include <sys/types.h>
53 #include <sys/event.h>
54 #include <sys/stat.h>
55 #include <sys/time.h>
56 #include <fcntl.h>
57
58 QT_BEGIN_NAMESPACE
59
60 // #define KEVENT_DEBUG
61 #ifdef KEVENT_DEBUG
62 # define DEBUG qDebug
63 #else
64 # define DEBUG if(false)qDebug
65 #endif
66
create(QObject * parent)67 QKqueueFileSystemWatcherEngine *QKqueueFileSystemWatcherEngine::create(QObject *parent)
68 {
69 int kqfd = kqueue();
70 if (kqfd == -1)
71 return 0;
72 return new QKqueueFileSystemWatcherEngine(kqfd, parent);
73 }
74
QKqueueFileSystemWatcherEngine(int kqfd,QObject * parent)75 QKqueueFileSystemWatcherEngine::QKqueueFileSystemWatcherEngine(int kqfd, QObject *parent)
76 : QFileSystemWatcherEngine(parent),
77 kqfd(kqfd),
78 notifier(kqfd, QSocketNotifier::Read, this)
79 {
80 connect(¬ifier, SIGNAL(activated(QSocketDescriptor)), SLOT(readFromKqueue()));
81
82 fcntl(kqfd, F_SETFD, FD_CLOEXEC);
83 }
84
~QKqueueFileSystemWatcherEngine()85 QKqueueFileSystemWatcherEngine::~QKqueueFileSystemWatcherEngine()
86 {
87 notifier.setEnabled(false);
88 close(kqfd);
89
90 for (int id : qAsConst(pathToID))
91 ::close(id < 0 ? -id : id);
92 }
93
addPaths(const QStringList & paths,QStringList * files,QStringList * directories)94 QStringList QKqueueFileSystemWatcherEngine::addPaths(const QStringList &paths,
95 QStringList *files,
96 QStringList *directories)
97 {
98 QStringList unhandled;
99 for (const QString &path : paths) {
100 auto sg = qScopeGuard([&]{unhandled.push_back(path);});
101 int fd;
102 #if defined(O_EVTONLY)
103 fd = qt_safe_open(QFile::encodeName(path), O_EVTONLY);
104 #else
105 fd = qt_safe_open(QFile::encodeName(path), O_RDONLY);
106 #endif
107 if (fd == -1) {
108 perror("QKqueueFileSystemWatcherEngine::addPaths: open");
109 continue;
110 }
111 if (fd >= (int)FD_SETSIZE / 2 && fd < (int)FD_SETSIZE) {
112 int fddup = qt_safe_dup(fd, FD_SETSIZE);
113 if (fddup != -1) {
114 ::close(fd);
115 fd = fddup;
116 }
117 }
118
119 QT_STATBUF st;
120 if (QT_FSTAT(fd, &st) == -1) {
121 perror("QKqueueFileSystemWatcherEngine::addPaths: fstat");
122 ::close(fd);
123 continue;
124 }
125 int id = (S_ISDIR(st.st_mode)) ? -fd : fd;
126 if (id < 0) {
127 if (directories->contains(path)) {
128 ::close(fd);
129 continue;
130 }
131 } else {
132 if (files->contains(path)) {
133 ::close(fd);
134 continue;
135 }
136 }
137
138 struct kevent kev;
139 EV_SET(&kev,
140 fd,
141 EVFILT_VNODE,
142 EV_ADD | EV_ENABLE | EV_CLEAR,
143 NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_RENAME | NOTE_REVOKE,
144 0,
145 0);
146 if (kevent(kqfd, &kev, 1, 0, 0, 0) == -1) {
147 perror("QKqueueFileSystemWatcherEngine::addPaths: kevent");
148 ::close(fd);
149 continue;
150 }
151
152 sg.dismiss();
153
154 if (id < 0) {
155 DEBUG() << "QKqueueFileSystemWatcherEngine: added directory path" << path;
156 directories->append(path);
157 } else {
158 DEBUG() << "QKqueueFileSystemWatcherEngine: added file path" << path;
159 files->append(path);
160 }
161
162 pathToID.insert(path, id);
163 idToPath.insert(id, path);
164 }
165
166 return unhandled;
167 }
168
removePaths(const QStringList & paths,QStringList * files,QStringList * directories)169 QStringList QKqueueFileSystemWatcherEngine::removePaths(const QStringList &paths,
170 QStringList *files,
171 QStringList *directories)
172 {
173 if (pathToID.isEmpty())
174 return paths;
175
176 QStringList unhandled;
177 for (const QString &path : paths) {
178 auto sg = qScopeGuard([&]{unhandled.push_back(path);});
179 int id = pathToID.take(path);
180 QString x = idToPath.take(id);
181 if (x.isEmpty() || x != path)
182 continue;
183
184 ::close(id < 0 ? -id : id);
185
186 sg.dismiss();
187
188 if (id < 0)
189 directories->removeAll(path);
190 else
191 files->removeAll(path);
192 }
193
194 return unhandled;
195 }
196
readFromKqueue()197 void QKqueueFileSystemWatcherEngine::readFromKqueue()
198 {
199 forever {
200 DEBUG() << "QKqueueFileSystemWatcherEngine: polling for changes";
201 int r;
202 struct kevent kev;
203 struct timespec ts = { 0, 0 }; // 0 ts, because we want to poll
204 EINTR_LOOP(r, kevent(kqfd, 0, 0, &kev, 1, &ts));
205 if (r < 0) {
206 perror("QKqueueFileSystemWatcherEngine: error during kevent wait");
207 return;
208 } else if (r == 0) {
209 // polling returned no events, so stop
210 break;
211 } else {
212 int fd = kev.ident;
213
214 DEBUG() << "QKqueueFileSystemWatcherEngine: processing kevent" << kev.ident << kev.filter;
215
216 int id = fd;
217 QString path = idToPath.value(id);
218 if (path.isEmpty()) {
219 // perhaps a directory?
220 id = -id;
221 path = idToPath.value(id);
222 if (path.isEmpty()) {
223 DEBUG() << "QKqueueFileSystemWatcherEngine: received a kevent for a file we're not watching";
224 continue;
225 }
226 }
227 if (kev.filter != EVFILT_VNODE) {
228 DEBUG() << "QKqueueFileSystemWatcherEngine: received a kevent with the wrong filter";
229 continue;
230 }
231
232 if ((kev.fflags & (NOTE_DELETE | NOTE_REVOKE | NOTE_RENAME)) != 0) {
233 DEBUG() << path << "removed, removing watch also";
234
235 pathToID.remove(path);
236 idToPath.remove(id);
237 ::close(fd);
238
239 if (id < 0)
240 emit directoryChanged(path, true);
241 else
242 emit fileChanged(path, true);
243 } else {
244 DEBUG() << path << "changed";
245
246 if (id < 0)
247 emit directoryChanged(path, false);
248 else
249 emit fileChanged(path, false);
250 }
251 }
252
253 }
254 }
255
256 QT_END_NAMESPACE
257