Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qfilesystemwatcher_inotify.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
7
8#include "private/qcore_unix_p.h"
9#include "private/qsystemerror_p.h"
10
11#include <qdebug.h>
12#include <qfile.h>
13#include <qfileinfo.h>
14#include <qscopeguard.h>
15#include <qsocketnotifier.h>
16#include <qvarlengtharray.h>
17
18#if defined(Q_OS_LINUX)
19#include <sys/syscall.h>
20#include <sys/ioctl.h>
21#include <unistd.h>
22#include <fcntl.h>
23#endif
24
25#if defined(QT_NO_INOTIFY)
26
27#if defined(Q_OS_QNX)
28// These files should only be compiled on QNX if the inotify headers are found
29#error "Should not get here."
30#endif
31
32#include <linux/types.h>
33
34#if defined(__i386__)
35# define __NR_inotify_init 291
36# define __NR_inotify_add_watch 292
37# define __NR_inotify_rm_watch 293
38# define __NR_inotify_init1 332
39#elif defined(__x86_64__)
40# define __NR_inotify_init 253
41# define __NR_inotify_add_watch 254
42# define __NR_inotify_rm_watch 255
43# define __NR_inotify_init1 294
44#elif defined(__powerpc__) || defined(__powerpc64__)
45# define __NR_inotify_init 275
46# define __NR_inotify_add_watch 276
47# define __NR_inotify_rm_watch 277
48# define __NR_inotify_init1 318
49#elif defined (__ia64__)
50# define __NR_inotify_init 1277
51# define __NR_inotify_add_watch 1278
52# define __NR_inotify_rm_watch 1279
53# define __NR_inotify_init1 1318
54#elif defined (__s390__) || defined (__s390x__)
55# define __NR_inotify_init 284
56# define __NR_inotify_add_watch 285
57# define __NR_inotify_rm_watch 286
58# define __NR_inotify_init1 324
59#elif defined (__alpha__)
60# define __NR_inotify_init 444
61# define __NR_inotify_add_watch 445
62# define __NR_inotify_rm_watch 446
63// no inotify_init1 for the Alpha
64#elif defined (__sparc__) || defined (__sparc64__)
65# define __NR_inotify_init 151
66# define __NR_inotify_add_watch 152
67# define __NR_inotify_rm_watch 156
68# define __NR_inotify_init1 322
69#elif defined (__arm__)
70# define __NR_inotify_init 316
71# define __NR_inotify_add_watch 317
72# define __NR_inotify_rm_watch 318
73# define __NR_inotify_init1 360
74#elif defined (__sh__)
75# define __NR_inotify_init 290
76# define __NR_inotify_add_watch 291
77# define __NR_inotify_rm_watch 292
78# define __NR_inotify_init1 332
79#elif defined (__sh64__)
80# define __NR_inotify_init 318
81# define __NR_inotify_add_watch 319
82# define __NR_inotify_rm_watch 320
83# define __NR_inotify_init1 360
84#elif defined (__mips__)
85# define __NR_inotify_init 284
86# define __NR_inotify_add_watch 285
87# define __NR_inotify_rm_watch 286
88# define __NR_inotify_init1 329
89#elif defined (__hppa__)
90# define __NR_inotify_init 269
91# define __NR_inotify_add_watch 270
92# define __NR_inotify_rm_watch 271
93# define __NR_inotify_init1 314
94#elif defined (__avr32__)
95# define __NR_inotify_init 240
96# define __NR_inotify_add_watch 241
97# define __NR_inotify_rm_watch 242
98// no inotify_init1 for AVR32
99#elif defined (__mc68000__)
100# define __NR_inotify_init 284
101# define __NR_inotify_add_watch 285
102# define __NR_inotify_rm_watch 286
103# define __NR_inotify_init1 328
104#elif defined (__aarch64__)
105# define __NR_inotify_init1 26
106# define __NR_inotify_add_watch 27
107# define __NR_inotify_rm_watch 28
108// no inotify_init for aarch64
109#else
110# error "This architecture is not supported. Please see http://www.qt-project.org/"
111#endif
112
113#if !defined(IN_CLOEXEC) && defined(O_CLOEXEC) && defined(__NR_inotify_init1)
114# define IN_CLOEXEC O_CLOEXEC
115#endif
116
117QT_BEGIN_NAMESPACE
118
119#ifdef QT_LINUXBASE
120// ### the LSB doesn't standardize syscall, need to wait until glib2.4 is standardized
121static inline int syscall(...) { return -1; }
122#endif
123
124static inline int inotify_init()
125{
126#ifdef __NR_inotify_init
127 return syscall(__NR_inotify_init);
128#else
129 return syscall(__NR_inotify_init1, 0);
130#endif
131}
132
133static inline int inotify_add_watch(int fd, const char *name, __u32 mask)
134{
135 return syscall(__NR_inotify_add_watch, fd, name, mask);
136}
137
138static inline int inotify_rm_watch(int fd, __u32 wd)
139{
140 return syscall(__NR_inotify_rm_watch, fd, wd);
141}
142
143#ifdef IN_CLOEXEC
144static inline int inotify_init1(int flags)
145{
146 return syscall(__NR_inotify_init1, flags);
147}
148#endif
149
150// the following struct and values are documented in linux/inotify.h
151extern "C" {
152
153struct inotify_event {
154 __s32 wd;
155 __u32 mask;
156 __u32 cookie;
157 __u32 len;
158 char name[0];
159};
160
161#define IN_ACCESS 0x00000001
162#define IN_MODIFY 0x00000002
163#define IN_ATTRIB 0x00000004
164#define IN_CLOSE_WRITE 0x00000008
165#define IN_CLOSE_NOWRITE 0x00000010
166#define IN_OPEN 0x00000020
167#define IN_MOVED_FROM 0x00000040
168#define IN_MOVED_TO 0x00000080
169#define IN_CREATE 0x00000100
170#define IN_DELETE 0x00000200
171#define IN_DELETE_SELF 0x00000400
172#define IN_MOVE_SELF 0x00000800
173#define IN_UNMOUNT 0x00002000
174#define IN_Q_OVERFLOW 0x00004000
175#define IN_IGNORED 0x00008000
176
177#define IN_CLOSE (IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)
178#define IN_MOVE (IN_MOVED_FROM | IN_MOVED_TO)
179}
180
181QT_END_NAMESPACE
182
183// --------- inotify.h end ----------
184
185#else /* QT_NO_INOTIFY */
186
187#include <sys/inotify.h>
188
189// see https://github.com/android-ndk/ndk/issues/394
190# if defined(Q_OS_ANDROID) && (__ANDROID_API__ < 21)
191static inline int inotify_init1(int flags)
192{
193 return syscall(__NR_inotify_init1, flags);
194}
195# endif
196
197#endif
198
199QT_BEGIN_NAMESPACE
200
201QInotifyFileSystemWatcherEngine *QInotifyFileSystemWatcherEngine::create(QObject *parent)
202{
203 int fd = -1;
204#if defined(IN_CLOEXEC)
205 fd = inotify_init1(IN_CLOEXEC);
206#endif
207 if (fd == -1) {
208 fd = inotify_init();
209 if (fd == -1)
210 return nullptr;
211 }
212 return new QInotifyFileSystemWatcherEngine(fd, parent);
213}
214
215QInotifyFileSystemWatcherEngine::QInotifyFileSystemWatcherEngine(int fd, QObject *parent)
216 : QFileSystemWatcherEngine(parent),
217 inotifyFd(fd),
218 notifier(fd, QSocketNotifier::Read, this)
219{
220 fcntl(inotifyFd, F_SETFD, FD_CLOEXEC);
221 QObject::connect(&notifier, &QSocketNotifier::activated,
222 this, &QInotifyFileSystemWatcherEngine::readFromInotify);
223}
224
225QInotifyFileSystemWatcherEngine::~QInotifyFileSystemWatcherEngine()
226{
227 notifier.setEnabled(false);
228 for (int id : std::as_const(pathToID))
229 inotify_rm_watch(inotifyFd, id < 0 ? -id : id);
230
231 ::close(inotifyFd);
232}
233
234QStringList QInotifyFileSystemWatcherEngine::addPaths(const QStringList &paths,
235 QStringList *files,
236 QStringList *directories)
237{
238 QStringList unhandled;
239 for (const QString &path : paths) {
240 QFileInfo fi(path);
241 bool isDir = fi.isDir();
242 auto sg = qScopeGuard([&]{ unhandled.push_back(path); });
243 if (isDir) {
244 if (directories->contains(path))
245 continue;
246 } else {
247 if (files->contains(path))
248 continue;
249 }
250
251 int wd = inotify_add_watch(inotifyFd,
252 QFile::encodeName(path),
253 (isDir
254 ? (0
255 | IN_ATTRIB
256 | IN_MOVE
257 | IN_CREATE
258 | IN_DELETE
259 | IN_DELETE_SELF
260 )
261 : (0
262 | IN_ATTRIB
263 | IN_MODIFY
264 | IN_MOVE
265 | IN_MOVE_SELF
266 | IN_DELETE_SELF
267 )));
268 if (wd < 0) {
269 if (errno != ENOENT)
270 qErrnoWarning("inotify_add_watch(%ls) failed:", path.constData());
271 continue;
272 }
273
274 sg.dismiss();
275
276 int id = isDir ? -wd : wd;
277 if (id < 0) {
278 directories->append(path);
279 } else {
280 files->append(path);
281 }
282
283 pathToID.insert(path, id);
284 idToPath.insert(id, path);
285 }
286
287 return unhandled;
288}
289
290QStringList QInotifyFileSystemWatcherEngine::removePaths(const QStringList &paths,
291 QStringList *files,
292 QStringList *directories)
293{
294 QStringList unhandled;
295 for (const QString &path : paths) {
296 int id = pathToID.take(path);
297
298 auto sg = qScopeGuard([&]{ unhandled.push_back(path); });
299
300 // Multiple paths could be associated to the same watch descriptor
301 // when a file is moved and added with the new name.
302 // So we should find and delete the correct one by using
303 // both id and path
304 auto path_range = idToPath.equal_range(id);
305 auto path_it = std::find(path_range.first, path_range.second, path);
306 if (path_it == idToPath.end())
307 continue;
308
309 const ssize_t num_elements = std::distance(path_range.first, path_range.second);
310 idToPath.erase(path_it);
311
312 // If there was only one path associated to the given id we should remove the watch
313 if (num_elements == 1) {
314 int wd = id < 0 ? -id : id;
315 inotify_rm_watch(inotifyFd, wd);
316 }
317
318 sg.dismiss();
319
320 if (id < 0) {
321 directories->removeAll(path);
322 } else {
323 files->removeAll(path);
324 }
325 }
326
327 return unhandled;
328}
329
330void QInotifyFileSystemWatcherEngine::readFromInotify()
331{
332 // qDebug("QInotifyFileSystemWatcherEngine::readFromInotify");
333
334 int buffSize = 0;
335 if (ioctl(inotifyFd, FIONREAD, (char *) &buffSize) == -1 || buffSize == 0)
336 return;
337
338 QVarLengthArray<char, 4096> buffer(buffSize);
339 buffSize = int(read(inotifyFd, buffer.data(), buffSize));
340 char *at = buffer.data();
341 char * const end = at + buffSize;
342
343 QHash<int, inotify_event *> eventForId;
344 while (at < end) {
345 inotify_event *event = reinterpret_cast<inotify_event *>(at);
346
347 if (eventForId.contains(event->wd))
348 eventForId[event->wd]->mask |= event->mask;
349 else
350 eventForId.insert(event->wd, event);
351
352 at += sizeof(inotify_event) + event->len;
353 }
354
355 QHash<int, inotify_event *>::const_iterator it = eventForId.constBegin();
356 while (it != eventForId.constEnd()) {
357 const inotify_event &event = **it;
358 ++it;
359
360 // qDebug() << "inotify event, wd" << event.wd << "mask" << Qt::hex << event.mask;
361
362 int id = event.wd;
363 QString path = getPathFromID(id);
364 if (path.isEmpty()) {
365 // perhaps a directory?
366 id = -id;
367 path = getPathFromID(id);
368 if (path.isEmpty())
369 continue;
370 }
371
372 // qDebug() << "event for path" << path;
373
374 if ((event.mask & (IN_DELETE_SELF | IN_MOVE_SELF | IN_UNMOUNT)) != 0) {
375 pathToID.remove(path);
376 idToPath.remove(id, getPathFromID(id));
377 if (!idToPath.contains(id))
378 inotify_rm_watch(inotifyFd, event.wd);
379
380 if (id < 0)
381 emit directoryChanged(path, true);
382 else
383 emit fileChanged(path, true);
384 } else {
385 if (id < 0)
386 emit directoryChanged(path, false);
387 else
388 emit fileChanged(path, false);
389 }
390 }
391}
392
393template <typename Hash, typename Key>
394typename Hash::const_iterator
395find_last_in_equal_range(const Hash &c, const Key &key)
396{
397 // find c.equal_range(key).second - 1 without backwards iteration:
398 auto i = c.find(key);
399 const auto end = c.cend();
400 if (i == end)
401 return end;
402 decltype(i) prev;
403 do {
404 prev = i;
405 ++i;
406 } while (i != end && i.key() == key);
407 return prev;
408}
409
410QString QInotifyFileSystemWatcherEngine::getPathFromID(int id) const
411{
412 auto i = find_last_in_equal_range(idToPath, id);
413 return i == idToPath.cend() ? QString() : i.value() ;
414}
415
416QT_END_NAMESPACE
417
418#include "moc_qfilesystemwatcher_inotify_p.cpp"
Hash::const_iterator find_last_in_equal_range(const Hash &c, const Key &key)