1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://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 http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://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 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file. Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qfilesystemwatcher.h"
43 #include "qfilesystemwatcher_inotify_p.h"
44
45 #ifndef QT_NO_FILESYSTEMWATCHER
46
47 #include "private/qcore_unix_p.h"
48
49 #include <qdebug.h>
50 #include <qfile.h>
51 #include <qfileinfo.h>
52 #include <qsocketnotifier.h>
53 #include <qvarlengtharray.h>
54
55 #if defined(Q_OS_LINUX)
56 #include <sys/syscall.h>
57 #include <sys/ioctl.h>
58 #include <unistd.h>
59 #include <fcntl.h>
60 #endif
61
62 #if defined(QT_NO_INOTIFY)
63
64 #if defined(Q_OS_QNX)
65 // These files should only be compiled on QNX if the inotify headers are found
66 #error "Should not get here."
67 #endif
68
69 #include <linux/types.h>
70
71 #if defined(__i386__)
72 # define __NR_inotify_init 291
73 # define __NR_inotify_add_watch 292
74 # define __NR_inotify_rm_watch 293
75 # define __NR_inotify_init1 332
76 #elif defined(__x86_64__)
77 # define __NR_inotify_init 253
78 # define __NR_inotify_add_watch 254
79 # define __NR_inotify_rm_watch 255
80 # define __NR_inotify_init1 294
81 #elif defined(__powerpc__) || defined(__powerpc64__)
82 # define __NR_inotify_init 275
83 # define __NR_inotify_add_watch 276
84 # define __NR_inotify_rm_watch 277
85 # define __NR_inotify_init1 318
86 #elif defined (__ia64__)
87 # define __NR_inotify_init 1277
88 # define __NR_inotify_add_watch 1278
89 # define __NR_inotify_rm_watch 1279
90 # define __NR_inotify_init1 1318
91 #elif defined (__s390__) || defined (__s390x__)
92 # define __NR_inotify_init 284
93 # define __NR_inotify_add_watch 285
94 # define __NR_inotify_rm_watch 286
95 # define __NR_inotify_init1 324
96 #elif defined (__alpha__)
97 # define __NR_inotify_init 444
98 # define __NR_inotify_add_watch 445
99 # define __NR_inotify_rm_watch 446
100 // no inotify_init1 for the Alpha
101 #elif defined (__sparc__) || defined (__sparc64__)
102 # define __NR_inotify_init 151
103 # define __NR_inotify_add_watch 152
104 # define __NR_inotify_rm_watch 156
105 # define __NR_inotify_init1 322
106 #elif defined (__arm__)
107 # define __NR_inotify_init 316
108 # define __NR_inotify_add_watch 317
109 # define __NR_inotify_rm_watch 318
110 # define __NR_inotify_init1 360
111 #elif defined (__sh__)
112 # define __NR_inotify_init 290
113 # define __NR_inotify_add_watch 291
114 # define __NR_inotify_rm_watch 292
115 # define __NR_inotify_init1 332
116 #elif defined (__sh64__)
117 # define __NR_inotify_init 318
118 # define __NR_inotify_add_watch 319
119 # define __NR_inotify_rm_watch 320
120 # define __NR_inotify_init1 360
121 #elif defined (__mips__)
122 # define __NR_inotify_init 284
123 # define __NR_inotify_add_watch 285
124 # define __NR_inotify_rm_watch 286
125 # define __NR_inotify_init1 329
126 #elif defined (__hppa__)
127 # define __NR_inotify_init 269
128 # define __NR_inotify_add_watch 270
129 # define __NR_inotify_rm_watch 271
130 # define __NR_inotify_init1 314
131 #elif defined (__avr32__)
132 # define __NR_inotify_init 240
133 # define __NR_inotify_add_watch 241
134 # define __NR_inotify_rm_watch 242
135 // no inotify_init1 for AVR32
136 #elif defined (__mc68000__)
137 # define __NR_inotify_init 284
138 # define __NR_inotify_add_watch 285
139 # define __NR_inotify_rm_watch 286
140 # define __NR_inotify_init1 328
141 #elif defined (__aarch64__)
142 # define __NR_inotify_init1 26
143 # define __NR_inotify_add_watch 27
144 # define __NR_inotify_rm_watch 28
145 // no inotify_init for aarch64
146 #else
147 # error "This architecture is not supported. Please talk to qt-bugs@trolltech.com"
148 #endif
149
150 #if !defined(IN_CLOEXEC) && defined(O_CLOEXEC) && defined(__NR_inotify_init1)
151 # define IN_CLOEXEC O_CLOEXEC
152 #endif
153
154 QT_BEGIN_NAMESPACE
155
156 #ifdef QT_LINUXBASE
157 // ### the LSB doesn't standardize syscall, need to wait until glib2.4 is standardized
syscall(...)158 static inline int syscall(...) { return -1; }
159 #endif
160
inotify_init()161 static inline int inotify_init()
162 {
163 #ifdef __NR_inotify_init
164 return syscall(__NR_inotify_init);
165 #else
166 return syscall(__NR_inotify_init1, 0);
167 #endif
168 }
169
inotify_add_watch(int fd,const char * name,__u32 mask)170 static inline int inotify_add_watch(int fd, const char *name, __u32 mask)
171 {
172 return syscall(__NR_inotify_add_watch, fd, name, mask);
173 }
174
inotify_rm_watch(int fd,__u32 wd)175 static inline int inotify_rm_watch(int fd, __u32 wd)
176 {
177 return syscall(__NR_inotify_rm_watch, fd, wd);
178 }
179
180 #ifdef IN_CLOEXEC
inotify_init1(int flags)181 static inline int inotify_init1(int flags)
182 {
183 return syscall(__NR_inotify_init1, flags);
184 }
185 #endif
186
187 // the following struct and values are documented in linux/inotify.h
188 extern "C" {
189
190 struct inotify_event {
191 __s32 wd;
192 __u32 mask;
193 __u32 cookie;
194 __u32 len;
195 char name[0];
196 };
197
198 #define IN_ACCESS 0x00000001
199 #define IN_MODIFY 0x00000002
200 #define IN_ATTRIB 0x00000004
201 #define IN_CLOSE_WRITE 0x00000008
202 #define IN_CLOSE_NOWRITE 0x00000010
203 #define IN_OPEN 0x00000020
204 #define IN_MOVED_FROM 0x00000040
205 #define IN_MOVED_TO 0x00000080
206 #define IN_CREATE 0x00000100
207 #define IN_DELETE 0x00000200
208 #define IN_DELETE_SELF 0x00000400
209 #define IN_MOVE_SELF 0x00000800
210 #define IN_UNMOUNT 0x00002000
211 #define IN_Q_OVERFLOW 0x00004000
212 #define IN_IGNORED 0x00008000
213
214 #define IN_CLOSE (IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)
215 #define IN_MOVE (IN_MOVED_FROM | IN_MOVED_TO)
216 }
217
218 QT_END_NAMESPACE
219
220 // --------- inotify.h end ----------
221
222 #else /* QT_NO_INOTIFY */
223
224 #include <sys/inotify.h>
225
226 #endif
227
228 QT_BEGIN_NAMESPACE
229
create()230 QInotifyFileSystemWatcherEngine *QInotifyFileSystemWatcherEngine::create()
231 {
232 int fd = -1;
233 #ifdef IN_CLOEXEC
234 fd = inotify_init1(IN_CLOEXEC);
235 #endif
236 if (fd == -1) {
237 fd = inotify_init();
238 if (fd == -1)
239 return 0;
240 ::fcntl(fd, F_SETFD, FD_CLOEXEC);
241 }
242 return new QInotifyFileSystemWatcherEngine(fd);
243 }
244
QInotifyFileSystemWatcherEngine(int fd)245 QInotifyFileSystemWatcherEngine::QInotifyFileSystemWatcherEngine(int fd)
246 : inotifyFd(fd)
247 {
248 fcntl(inotifyFd, F_SETFD, FD_CLOEXEC);
249
250 moveToThread(this);
251 }
252
~QInotifyFileSystemWatcherEngine()253 QInotifyFileSystemWatcherEngine::~QInotifyFileSystemWatcherEngine()
254 {
255 foreach (int id, pathToID)
256 inotify_rm_watch(inotifyFd, id < 0 ? -id : id);
257
258 ::close(inotifyFd);
259 }
260
run()261 void QInotifyFileSystemWatcherEngine::run()
262 {
263 QSocketNotifier sn(inotifyFd, QSocketNotifier::Read, this);
264 connect(&sn, SIGNAL(activated(int)), SLOT(readFromInotify()));
265 (void) exec();
266 }
267
addPaths(const QStringList & paths,QStringList * files,QStringList * directories)268 QStringList QInotifyFileSystemWatcherEngine::addPaths(const QStringList &paths,
269 QStringList *files,
270 QStringList *directories)
271 {
272 QMutexLocker locker(&mutex);
273
274 QStringList p = paths;
275 QMutableListIterator<QString> it(p);
276 while (it.hasNext()) {
277 QString path = it.next();
278 QFileInfo fi(path);
279 bool isDir = fi.isDir();
280 if (isDir) {
281 if (directories->contains(path))
282 continue;
283 } else {
284 if (files->contains(path))
285 continue;
286 }
287
288 int wd = inotify_add_watch(inotifyFd,
289 QFile::encodeName(path),
290 (isDir
291 ? (0
292 | IN_ATTRIB
293 | IN_MOVE
294 | IN_CREATE
295 | IN_DELETE
296 | IN_DELETE_SELF
297 )
298 : (0
299 | IN_ATTRIB
300 | IN_MODIFY
301 | IN_MOVE
302 | IN_MOVE_SELF
303 | IN_DELETE_SELF
304 )));
305 if (wd <= 0) {
306 perror("QInotifyFileSystemWatcherEngine::addPaths: inotify_add_watch failed");
307 continue;
308 }
309
310 it.remove();
311
312 int id = isDir ? -wd : wd;
313 if (id < 0) {
314 directories->append(path);
315 } else {
316 files->append(path);
317 }
318
319 pathToID.insert(path, id);
320 idToPath.insert(id, path);
321 }
322
323 start();
324
325 return p;
326 }
327
removePaths(const QStringList & paths,QStringList * files,QStringList * directories)328 QStringList QInotifyFileSystemWatcherEngine::removePaths(const QStringList &paths,
329 QStringList *files,
330 QStringList *directories)
331 {
332 QMutexLocker locker(&mutex);
333
334 QStringList p = paths;
335 QMutableListIterator<QString> it(p);
336 while (it.hasNext()) {
337 QString path = it.next();
338 int id = pathToID.take(path);
339 QString x = idToPath.take(id);
340 if (x.isEmpty() || x != path)
341 continue;
342
343 int wd = id < 0 ? -id : id;
344 // qDebug() << "removing watch for path" << path << "wd" << wd;
345 inotify_rm_watch(inotifyFd, wd);
346
347 it.remove();
348 if (id < 0) {
349 directories->removeAll(path);
350 } else {
351 files->removeAll(path);
352 }
353 }
354
355 return p;
356 }
357
stop()358 void QInotifyFileSystemWatcherEngine::stop()
359 {
360 quit();
361 }
362
readFromInotify()363 void QInotifyFileSystemWatcherEngine::readFromInotify()
364 {
365 QMutexLocker locker(&mutex);
366
367 // qDebug() << "QInotifyFileSystemWatcherEngine::readFromInotify";
368
369 int buffSize = 0;
370 ioctl(inotifyFd, FIONREAD, (char *) &buffSize);
371 QVarLengthArray<char, 4096> buffer(buffSize);
372 buffSize = read(inotifyFd, buffer.data(), buffSize);
373 char *at = buffer.data();
374 char * const end = at + buffSize;
375
376 QHash<int, inotify_event *> eventForId;
377 while (at < end) {
378 inotify_event *event = reinterpret_cast<inotify_event *>(at);
379
380 if (eventForId.contains(event->wd))
381 eventForId[event->wd]->mask |= event->mask;
382 else
383 eventForId.insert(event->wd, event);
384
385 at += sizeof(inotify_event) + event->len;
386 }
387
388 QHash<int, inotify_event *>::const_iterator it = eventForId.constBegin();
389 while (it != eventForId.constEnd()) {
390 const inotify_event &event = **it;
391 ++it;
392
393 // qDebug() << "inotify event, wd" << event.wd << "mask" << hex << event.mask;
394
395 int id = event.wd;
396 QString path = idToPath.value(id);
397 if (path.isEmpty()) {
398 // perhaps a directory?
399 id = -id;
400 path = idToPath.value(id);
401 if (path.isEmpty())
402 continue;
403 }
404
405 // qDebug() << "event for path" << path;
406
407 if ((event.mask & (IN_DELETE_SELF | IN_MOVE_SELF | IN_UNMOUNT)) != 0) {
408 pathToID.remove(path);
409 idToPath.remove(id);
410 inotify_rm_watch(inotifyFd, event.wd);
411
412 if (id < 0)
413 emit directoryChanged(path, true);
414 else
415 emit fileChanged(path, true);
416 } else {
417 if (id < 0)
418 emit directoryChanged(path, false);
419 else
420 emit fileChanged(path, false);
421 }
422 }
423 }
424
425 QT_END_NAMESPACE
426
427 #endif // QT_NO_FILESYSTEMWATCHER
428