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(&notifier, 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