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

source code of qtbase/src/corelib/io/qfilesystemwatcher_inotify.cpp