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