1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://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 https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://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 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qfilesystemwatcher.h"
41#include "qfilesystemwatcher_p.h"
42
43#include <qdatetime.h>
44#include <qdebug.h>
45#include <qdir.h>
46#include <qfileinfo.h>
47#include <qset.h>
48#include <qtimer.h>
49
50#if (defined(Q_OS_LINUX) || defined(Q_OS_QNX)) && QT_CONFIG(inotify)
51#define USE_INOTIFY
52#endif
53
54#include "qfilesystemwatcher_polling_p.h"
55#if defined(Q_OS_WIN)
56# include "qfilesystemwatcher_win_p.h"
57#elif defined(USE_INOTIFY)
58# include "qfilesystemwatcher_inotify_p.h"
59#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) || defined(Q_OS_OPENBSD) || defined(QT_PLATFORM_UIKIT)
60# include "qfilesystemwatcher_kqueue_p.h"
61#elif defined(Q_OS_OSX)
62# include "qfilesystemwatcher_fsevents_p.h"
63#endif
64
65#include <algorithm>
66#include <iterator>
67
68QT_BEGIN_NAMESPACE
69
70QFileSystemWatcherEngine *QFileSystemWatcherPrivate::createNativeEngine(QObject *parent)
71{
72#if defined(Q_OS_WIN)
73 return new QWindowsFileSystemWatcherEngine(parent);
74#elif defined(USE_INOTIFY)
75 // there is a chance that inotify may fail on Linux pre-2.6.13 (August
76 // 2005), so we can't just new inotify directly.
77 return QInotifyFileSystemWatcherEngine::create(parent);
78#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) || defined(Q_OS_OPENBSD) || defined(QT_PLATFORM_UIKIT)
79 return QKqueueFileSystemWatcherEngine::create(parent);
80#elif defined(Q_OS_OSX)
81 return QFseventsFileSystemWatcherEngine::create(parent);
82#else
83 Q_UNUSED(parent);
84 return 0;
85#endif
86}
87
88QFileSystemWatcherPrivate::QFileSystemWatcherPrivate()
89 : native(0), poller(0)
90{
91}
92
93void QFileSystemWatcherPrivate::init()
94{
95 Q_Q(QFileSystemWatcher);
96 native = createNativeEngine(q);
97 if (native) {
98 QObject::connect(native,
99 SIGNAL(fileChanged(QString,bool)),
100 q,
101 SLOT(_q_fileChanged(QString,bool)));
102 QObject::connect(native,
103 SIGNAL(directoryChanged(QString,bool)),
104 q,
105 SLOT(_q_directoryChanged(QString,bool)));
106#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
107 QObject::connect(static_cast<QWindowsFileSystemWatcherEngine *>(native),
108 &QWindowsFileSystemWatcherEngine::driveLockForRemoval,
109 q, [this] (const QString &p) { _q_winDriveLockForRemoval(p); });
110 QObject::connect(static_cast<QWindowsFileSystemWatcherEngine *>(native),
111 &QWindowsFileSystemWatcherEngine::driveLockForRemovalFailed,
112 q, [this] (const QString &p) { _q_winDriveLockForRemovalFailed(p); });
113 QObject::connect(static_cast<QWindowsFileSystemWatcherEngine *>(native),
114 &QWindowsFileSystemWatcherEngine::driveRemoved,
115 q, [this] (const QString &p) { _q_winDriveRemoved(p); });
116#endif // !Q_OS_WINRT
117 }
118}
119
120void QFileSystemWatcherPrivate::initPollerEngine()
121{
122 if(poller)
123 return;
124
125 Q_Q(QFileSystemWatcher);
126 poller = new QPollingFileSystemWatcherEngine(q); // that was a mouthful
127 QObject::connect(poller,
128 SIGNAL(fileChanged(QString,bool)),
129 q,
130 SLOT(_q_fileChanged(QString,bool)));
131 QObject::connect(poller,
132 SIGNAL(directoryChanged(QString,bool)),
133 q,
134 SLOT(_q_directoryChanged(QString,bool)));
135}
136
137void QFileSystemWatcherPrivate::_q_fileChanged(const QString &path, bool removed)
138{
139 Q_Q(QFileSystemWatcher);
140 if (!files.contains(path)) {
141 // the path was removed after a change was detected, but before we delivered the signal
142 return;
143 }
144 if (removed)
145 files.removeAll(path);
146 emit q->fileChanged(path, QFileSystemWatcher::QPrivateSignal());
147}
148
149void QFileSystemWatcherPrivate::_q_directoryChanged(const QString &path, bool removed)
150{
151 Q_Q(QFileSystemWatcher);
152 if (!directories.contains(path)) {
153 // perhaps the path was removed after a change was detected, but before we delivered the signal
154 return;
155 }
156 if (removed)
157 directories.removeAll(path);
158 emit q->directoryChanged(path, QFileSystemWatcher::QPrivateSignal());
159}
160
161#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
162
163void QFileSystemWatcherPrivate::_q_winDriveLockForRemoval(const QString &path)
164{
165 // Windows: Request to lock a (removable/USB) drive for removal, release
166 // its paths under watch, temporarily storing them should the lock fail.
167 Q_Q(QFileSystemWatcher);
168 QStringList pathsToBeRemoved;
169 auto pred = [&path] (const QString &f) { return !f.startsWith(path, Qt::CaseInsensitive); };
170 std::remove_copy_if(files.cbegin(), files.cend(),
171 std::back_inserter(pathsToBeRemoved), pred);
172 std::remove_copy_if(directories.cbegin(), directories.cend(),
173 std::back_inserter(pathsToBeRemoved), pred);
174 if (!pathsToBeRemoved.isEmpty()) {
175 q->removePaths(pathsToBeRemoved);
176 temporarilyRemovedPaths.insert(path.at(0), pathsToBeRemoved);
177 }
178}
179
180void QFileSystemWatcherPrivate::_q_winDriveLockForRemovalFailed(const QString &path)
181{
182 // Windows: Request to lock a (removable/USB) drive failed (blocked by other
183 // application), restore the watched paths.
184 Q_Q(QFileSystemWatcher);
185 if (!path.isEmpty()) {
186 const auto it = temporarilyRemovedPaths.find(path.at(0));
187 if (it != temporarilyRemovedPaths.end()) {
188 q->addPaths(it.value());
189 temporarilyRemovedPaths.erase(it);
190 }
191 }
192}
193
194void QFileSystemWatcherPrivate::_q_winDriveRemoved(const QString &path)
195{
196 // Windows: Drive finally removed, clear out paths stored in lock request.
197 if (!path.isEmpty())
198 temporarilyRemovedPaths.remove(path.at(0));
199}
200#endif // Q_OS_WIN && !Q_OS_WINRT
201
202/*!
203 \class QFileSystemWatcher
204 \inmodule QtCore
205 \brief The QFileSystemWatcher class provides an interface for monitoring files and directories for modifications.
206 \ingroup io
207 \since 4.2
208 \reentrant
209
210 QFileSystemWatcher monitors the file system for changes to files
211 and directories by watching a list of specified paths.
212
213 Call addPath() to watch a particular file or directory. Multiple
214 paths can be added using the addPaths() function. Existing paths can
215 be removed by using the removePath() and removePaths() functions.
216
217 QFileSystemWatcher examines each path added to it. Files that have
218 been added to the QFileSystemWatcher can be accessed using the
219 files() function, and directories using the directories() function.
220
221 The fileChanged() signal is emitted when a file has been modified,
222 renamed or removed from disk. Similarly, the directoryChanged()
223 signal is emitted when a directory or its contents is modified or
224 removed. Note that QFileSystemWatcher stops monitoring files once
225 they have been renamed or removed from disk, and directories once
226 they have been removed from disk.
227
228 \list
229 \li \b Notes:
230 \list
231 \li On systems running a Linux kernel without inotify support,
232 file systems that contain watched paths cannot be unmounted.
233
234 \li The act of monitoring files and directories for
235 modifications consumes system resources. This implies there is a
236 limit to the number of files and directories your process can
237 monitor simultaneously. On all BSD variants, for
238 example, an open file descriptor is required for each monitored
239 file. Some system limits the number of open file descriptors to 256
240 by default. This means that addPath() and addPaths() will fail if
241 your process tries to add more than 256 files or directories to
242 the file system monitor. Also note that your process may have
243 other file descriptors open in addition to the ones for files
244 being monitored, and these other open descriptors also count in
245 the total. \macos uses a different backend and does not
246 suffer from this issue.
247 \endlist
248 \endlist
249
250 \sa QFile, QDir
251*/
252
253
254/*!
255 Constructs a new file system watcher object with the given \a parent.
256*/
257QFileSystemWatcher::QFileSystemWatcher(QObject *parent)
258 : QObject(*new QFileSystemWatcherPrivate, parent)
259{
260 d_func()->init();
261}
262
263/*!
264 Constructs a new file system watcher object with the given \a parent
265 which monitors the specified \a paths list.
266*/
267QFileSystemWatcher::QFileSystemWatcher(const QStringList &paths, QObject *parent)
268 : QObject(*new QFileSystemWatcherPrivate, parent)
269{
270 d_func()->init();
271 addPaths(paths);
272}
273
274/*!
275 Destroys the file system watcher.
276*/
277QFileSystemWatcher::~QFileSystemWatcher()
278{ }
279
280/*!
281 Adds \a path to the file system watcher if \a path exists. The
282 path is not added if it does not exist, or if it is already being
283 monitored by the file system watcher.
284
285 If \a path specifies a directory, the directoryChanged() signal
286 will be emitted when \a path is modified or removed from disk;
287 otherwise the fileChanged() signal is emitted when \a path is
288 modified, renamed or removed.
289
290 If the watch was successful, true is returned.
291
292 Reasons for a watch failure are generally system-dependent, but
293 may include the resource not existing, access failures, or the
294 total watch count limit, if the platform has one.
295
296 \note There may be a system dependent limit to the number of
297 files and directories that can be monitored simultaneously.
298 If this limit is been reached, \a path will not be monitored,
299 and false is returned.
300
301 \sa addPaths(), removePath()
302*/
303bool QFileSystemWatcher::addPath(const QString &path)
304{
305 if (path.isEmpty()) {
306 qWarning("QFileSystemWatcher::addPath: path is empty");
307 return true;
308 }
309
310 QStringList paths = addPaths(QStringList(path));
311 return paths.isEmpty();
312}
313
314static QStringList empty_paths_pruned(const QStringList &paths)
315{
316 QStringList p;
317 p.reserve(paths.size());
318 const auto isEmpty = [](const QString &s) { return s.isEmpty(); };
319 std::remove_copy_if(paths.begin(), paths.end(),
320 std::back_inserter(p),
321 isEmpty);
322 return p;
323}
324
325/*!
326 Adds each path in \a paths to the file system watcher. Paths are
327 not added if they not exist, or if they are already being
328 monitored by the file system watcher.
329
330 If a path specifies a directory, the directoryChanged() signal
331 will be emitted when the path is modified or removed from disk;
332 otherwise the fileChanged() signal is emitted when the path is
333 modified, renamed, or removed.
334
335 The return value is a list of paths that could not be watched.
336
337 Reasons for a watch failure are generally system-dependent, but
338 may include the resource not existing, access failures, or the
339 total watch count limit, if the platform has one.
340
341 \note There may be a system dependent limit to the number of
342 files and directories that can be monitored simultaneously.
343 If this limit has been reached, the excess \a paths will not
344 be monitored, and they will be added to the returned QStringList.
345
346 \sa addPath(), removePaths()
347*/
348QStringList QFileSystemWatcher::addPaths(const QStringList &paths)
349{
350 Q_D(QFileSystemWatcher);
351
352 QStringList p = empty_paths_pruned(paths);
353
354 if (p.isEmpty()) {
355 qWarning("QFileSystemWatcher::addPaths: list is empty");
356 return p;
357 }
358
359 const auto selectEngine = [this, d]() -> QFileSystemWatcherEngine* {
360#ifdef QT_BUILD_INTERNAL
361 const QString on = objectName();
362
363 if (Q_UNLIKELY(on.startsWith(QLatin1String("_qt_autotest_force_engine_")))) {
364 // Autotest override case - use the explicitly selected engine only
365 const QStringRef forceName = on.midRef(26);
366 if (forceName == QLatin1String("poller")) {
367 qDebug("QFileSystemWatcher: skipping native engine, using only polling engine");
368 d_func()->initPollerEngine();
369 return d->poller;
370 } else if (forceName == QLatin1String("native")) {
371 qDebug("QFileSystemWatcher: skipping polling engine, using only native engine");
372 return d->native;
373 }
374 return nullptr;
375 }
376#endif
377 // Normal runtime case - search intelligently for best engine
378 if(d->native) {
379 return d->native;
380 } else {
381 d_func()->initPollerEngine();
382 return d->poller;
383 }
384 };
385
386 if (auto engine = selectEngine())
387 p = engine->addPaths(p, &d->files, &d->directories);
388
389 return p;
390}
391
392/*!
393 Removes the specified \a path from the file system watcher.
394
395 If the watch is successfully removed, true is returned.
396
397 Reasons for watch removal failing are generally system-dependent,
398 but may be due to the path having already been deleted, for example.
399
400 \sa removePaths(), addPath()
401*/
402bool QFileSystemWatcher::removePath(const QString &path)
403{
404 if (path.isEmpty()) {
405 qWarning("QFileSystemWatcher::removePath: path is empty");
406 return true;
407 }
408
409 QStringList paths = removePaths(QStringList(path));
410 return paths.isEmpty();
411}
412
413/*!
414 Removes the specified \a paths from the file system watcher.
415
416 The return value is a list of paths which were not able to be
417 unwatched successfully.
418
419 Reasons for watch removal failing are generally system-dependent,
420 but may be due to the path having already been deleted, for example.
421
422 \sa removePath(), addPaths()
423*/
424QStringList QFileSystemWatcher::removePaths(const QStringList &paths)
425{
426 Q_D(QFileSystemWatcher);
427
428 QStringList p = empty_paths_pruned(paths);
429
430 if (p.isEmpty()) {
431 qWarning("QFileSystemWatcher::removePaths: list is empty");
432 return p;
433 }
434
435 if (d->native)
436 p = d->native->removePaths(p, &d->files, &d->directories);
437 if (d->poller)
438 p = d->poller->removePaths(p, &d->files, &d->directories);
439
440 return p;
441}
442
443/*!
444 \fn void QFileSystemWatcher::fileChanged(const QString &path)
445
446 This signal is emitted when the file at the specified \a path is
447 modified, renamed or removed from disk.
448
449 \sa directoryChanged()
450*/
451
452/*!
453 \fn void QFileSystemWatcher::directoryChanged(const QString &path)
454
455 This signal is emitted when the directory at a specified \a path
456 is modified (e.g., when a file is added or deleted) or removed
457 from disk. Note that if there are several changes during a short
458 period of time, some of the changes might not emit this signal.
459 However, the last change in the sequence of changes will always
460 generate this signal.
461
462 \sa fileChanged()
463*/
464
465/*!
466 \fn QStringList QFileSystemWatcher::directories() const
467
468 Returns a list of paths to directories that are being watched.
469
470 \sa files()
471*/
472
473/*!
474 \fn QStringList QFileSystemWatcher::files() const
475
476 Returns a list of paths to files that are being watched.
477
478 \sa directories()
479*/
480
481QStringList QFileSystemWatcher::directories() const
482{
483 Q_D(const QFileSystemWatcher);
484 return d->directories;
485}
486
487QStringList QFileSystemWatcher::files() const
488{
489 Q_D(const QFileSystemWatcher);
490 return d->files;
491}
492
493QT_END_NAMESPACE
494
495#include "moc_qfilesystemwatcher.cpp"
496#include "moc_qfilesystemwatcher_p.cpp"
497
498