1/****************************************************************************
2**
3** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
4** Contact: http://www.qt-project.org/legal
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 Digia. For licensing terms and
14** conditions see http://qt.digia.com/licensing. For further information
15** use the contact form at http://qt.digia.com/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 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 2.1 requirements
23** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24**
25** In addition, as a special exception, Digia gives you certain additional
26** rights. These rights are described in the Digia Qt LGPL Exception
27** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28**
29** GNU General Public License Usage
30** Alternatively, this file may be used under the terms of the GNU
31** General Public License version 3.0 as published by the Free Software
32** Foundation and appearing in the file LICENSE.GPL included in the
33** packaging of this file. Please review the following information to
34** ensure the GNU General Public License version 3.0 requirements will be
35** met: http://www.gnu.org/copyleft/gpl.html.
36**
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
154QT_BEGIN_NAMESPACE
155
156#ifdef QT_LINUXBASE
157// ### the LSB doesn't standardize syscall, need to wait until glib2.4 is standardized
158static inline int syscall(...) { return -1; }
159#endif
160
161static 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
170static 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
175static 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
181static 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
188extern "C" {
189
190struct 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
218QT_END_NAMESPACE
219
220// --------- inotify.h end ----------
221
222#else /* QT_NO_INOTIFY */
223
224#include <sys/inotify.h>
225
226#endif
227
228QT_BEGIN_NAMESPACE
229
230QInotifyFileSystemWatcherEngine *QInotifyFileSystemWatcherEngine::create()
231{
232 register 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
245QInotifyFileSystemWatcherEngine::QInotifyFileSystemWatcherEngine(int fd)
246 : inotifyFd(fd)
247{
248 fcntl(inotifyFd, F_SETFD, FD_CLOEXEC);
249
250 moveToThread(this);
251}
252
253QInotifyFileSystemWatcherEngine::~QInotifyFileSystemWatcherEngine()
254{
255 foreach (int id, pathToID)
256 inotify_rm_watch(inotifyFd, id < 0 ? -id : id);
257
258 ::close(inotifyFd);
259}
260
261void QInotifyFileSystemWatcherEngine::run()
262{
263 QSocketNotifier sn(inotifyFd, QSocketNotifier::Read, this);
264 connect(&sn, SIGNAL(activated(int)), SLOT(readFromInotify()));
265 (void) exec();
266}
267
268QStringList 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
328QStringList 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
358void QInotifyFileSystemWatcherEngine::stop()
359{
360 quit();
361}
362
363void 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
425QT_END_NAMESPACE
426
427#endif // QT_NO_FILESYSTEMWATCHER
428