Warning: That file was not part of the compilation database. It may have many parsing errors.

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_win_p.h"
42
43#include <qdebug.h>
44#include <qfileinfo.h>
45#include <qstringlist.h>
46#include <qset.h>
47#include <qscopeguard.h>
48#include <qdatetime.h>
49#include <qdir.h>
50#include <qtextstream.h>
51
52#include <qt_windows.h>
53
54#ifndef Q_OS_WINRT
55# include <qabstractnativeeventfilter.h>
56# include <qcoreapplication.h>
57# include <qdir.h>
58# include <private/qeventdispatcher_win_p.h>
59# include <private/qthread_p.h>
60# include <dbt.h>
61# include <algorithm>
62# include <vector>
63#endif // !Q_OS_WINRT
64
65QT_BEGIN_NAMESPACE
66
67// #define WINQFSW_DEBUG
68#ifdef WINQFSW_DEBUG
69# define DEBUG qDebug
70#else
71# define DEBUG if (false) qDebug
72#endif
73
74static Qt::HANDLE createChangeNotification(const QString &path, uint flags)
75{
76 // Volume and folder paths need a trailing slash for proper notification
77 // (e.g. "c:" -> "c:/").
78 QString nativePath = QDir::toNativeSeparators(path);
79 if ((flags & FILE_NOTIFY_CHANGE_ATTRIBUTES) == 0 && !nativePath.endsWith(QLatin1Char('\\')))
80 nativePath.append(QLatin1Char('\\'));
81 const HANDLE result = FindFirstChangeNotification(reinterpret_cast<const wchar_t *>(nativePath.utf16()),
82 FALSE, flags);
83 DEBUG() << __FUNCTION__ << nativePath << Qt::hex <<showbase << flags << "returns" << result;
84 return result;
85}
86
87#ifndef Q_OS_WINRT
88///////////
89// QWindowsRemovableDriveListener
90// Listen for the various WM_DEVICECHANGE message indicating drive addition/removal
91// requests and removals.
92///////////
93class QWindowsRemovableDriveListener : public QObject, public QAbstractNativeEventFilter
94{
95 Q_OBJECT
96public:
97 // Device UUids as declared in ioevent.h (GUID_IO_VOLUME_LOCK, ...)
98 enum VolumeUuid { UnknownUuid, UuidIoVolumeLock, UuidIoVolumeLockFailed,
99 UuidIoVolumeUnlock, UuidIoMediaRemoval };
100
101 struct RemovableDriveEntry {
102 HDEVNOTIFY devNotify;
103 wchar_t drive;
104 };
105
106 explicit QWindowsRemovableDriveListener(QObject *parent = nullptr);
107 ~QWindowsRemovableDriveListener();
108
109 // Call from QFileSystemWatcher::addPaths() to set up notifications on drives
110 void addPath(const QString &path);
111
112#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
113 bool nativeEventFilter(const QByteArray &, void *messageIn, qintptr *) override;
114#else
115 bool nativeEventFilter(const QByteArray &, void *messageIn, long *) override;
116#endif
117
118signals:
119 void driveAdded();
120 void driveRemoved(); // Some drive removed
121 void driveRemoved(const QString &); // Watched/known drive removed
122 void driveLockForRemoval(const QString &);
123 void driveLockForRemovalFailed(const QString &);
124
125private:
126 static VolumeUuid volumeUuid(const UUID &needle);
127 void handleDbtCustomEvent(const MSG *msg);
128 void handleDbtDriveArrivalRemoval(const MSG *msg);
129
130 std::vector<RemovableDriveEntry> m_removableDrives;
131 quintptr m_lastMessageHash;
132};
133
134QWindowsRemovableDriveListener::QWindowsRemovableDriveListener(QObject *parent)
135 : QObject(parent)
136 , m_lastMessageHash(0)
137{
138}
139
140static void stopDeviceNotification(QWindowsRemovableDriveListener::RemovableDriveEntry &e)
141{
142 UnregisterDeviceNotification(e.devNotify);
143 e.devNotify = 0;
144}
145
146template <class Iterator> // Search sequence of RemovableDriveEntry for HDEVNOTIFY.
147static inline Iterator findByHDevNotify(Iterator i1, Iterator i2, HDEVNOTIFY hdevnotify)
148{
149 return std::find_if(i1, i2,
150 [hdevnotify] (const QWindowsRemovableDriveListener::RemovableDriveEntry &e) { return e.devNotify == hdevnotify; });
151}
152
153QWindowsRemovableDriveListener::~QWindowsRemovableDriveListener()
154{
155 std::for_each(m_removableDrives.begin(), m_removableDrives.end(), stopDeviceNotification);
156}
157
158static QString pathFromEntry(const QWindowsRemovableDriveListener::RemovableDriveEntry &re)
159{
160 QString path = QStringLiteral("A:/");
161 path[0] = QChar::fromLatin1(re.drive);
162 return path;
163}
164
165// Handle WM_DEVICECHANGE+DBT_CUSTOMEVENT, which is sent based on the registration
166// on the volume handle with QEventDispatcherWin32's message window in the class.
167// Capture the GUID_IO_VOLUME_LOCK indicating the drive is to be removed.
168QWindowsRemovableDriveListener::VolumeUuid QWindowsRemovableDriveListener::volumeUuid(const UUID &needle)
169{
170 static const struct VolumeUuidMapping // UUIDs from IoEvent.h (missing in MinGW)
171 {
172 VolumeUuid v;
173 UUID uuid;
174 } mapping[] = {
175 { UuidIoVolumeLock, // GUID_IO_VOLUME_LOCK
176 {0x50708874, 0xc9af, 0x11d1, {0x8f, 0xef, 0x0, 0xa0, 0xc9, 0xa0, 0x6d, 0x32}} },
177 { UuidIoVolumeLockFailed, // GUID_IO_VOLUME_LOCK_FAILED
178 {0xae2eed10, 0x0ba8, 0x11d2, {0x8f, 0xfb, 0x0, 0xa0, 0xc9, 0xa0, 0x6d, 0x32}} },
179 { UuidIoVolumeUnlock, // GUID_IO_VOLUME_UNLOCK
180 {0x9a8c3d68, 0xd0cb, 0x11d1, {0x8f, 0xef, 0x0, 0xa0, 0xc9, 0xa0, 0x6d, 0x32}} },
181 { UuidIoMediaRemoval, // GUID_IO_MEDIA_REMOVAL
182 {0xd07433c1, 0xa98e, 0x11d2, {0x91, 0x7a, 0x0, 0xa0, 0xc9, 0x06, 0x8f, 0xf3}} }
183 };
184
185 static const VolumeUuidMapping *end = mapping + sizeof(mapping) / sizeof(mapping[0]);
186 const VolumeUuidMapping *m =
187 std::find_if(mapping, end, [&needle] (const VolumeUuidMapping &m) { return IsEqualGUID(m.uuid, needle); });
188 return m != end ? m->v : UnknownUuid;
189}
190
191inline void QWindowsRemovableDriveListener::handleDbtCustomEvent(const MSG *msg)
192{
193 const DEV_BROADCAST_HDR *broadcastHeader = reinterpret_cast<const DEV_BROADCAST_HDR *>(msg->lParam);
194 if (broadcastHeader->dbch_devicetype != DBT_DEVTYP_HANDLE)
195 return;
196 const DEV_BROADCAST_HANDLE *broadcastHandle = reinterpret_cast<const DEV_BROADCAST_HANDLE *>(broadcastHeader);
197 const auto it = findByHDevNotify(m_removableDrives.cbegin(), m_removableDrives.cend(),
198 broadcastHandle->dbch_hdevnotify);
199 if (it == m_removableDrives.cend())
200 return;
201 switch (volumeUuid(broadcastHandle->dbch_eventguid)) {
202 case UuidIoVolumeLock: // Received for removable USB media
203 emit driveLockForRemoval(pathFromEntry(*it));
204 break;
205 case UuidIoVolumeLockFailed:
206 emit driveLockForRemovalFailed(pathFromEntry(*it));
207 break;
208 case UuidIoVolumeUnlock:
209 break;
210 case UuidIoMediaRemoval: // Received for optical drives
211 break;
212 default:
213 break;
214 }
215}
216
217// Handle WM_DEVICECHANGE+DBT_DEVICEARRIVAL/DBT_DEVICEREMOVECOMPLETE which are
218// sent to all top level windows and cannot be registered for (that is, their
219// triggering depends on top level windows being present)
220inline void QWindowsRemovableDriveListener::handleDbtDriveArrivalRemoval(const MSG *msg)
221{
222 const DEV_BROADCAST_HDR *broadcastHeader = reinterpret_cast<const DEV_BROADCAST_HDR *>(msg->lParam);
223 switch (broadcastHeader->dbch_devicetype) {
224 case DBT_DEVTYP_HANDLE: // WM_DEVICECHANGE/DBT_DEVTYP_HANDLE is sent for our registered drives.
225 if (msg->wParam == DBT_DEVICEREMOVECOMPLETE) {
226 const DEV_BROADCAST_HANDLE *broadcastHandle = reinterpret_cast<const DEV_BROADCAST_HANDLE *>(broadcastHeader);
227 const auto it = findByHDevNotify(m_removableDrives.begin(), m_removableDrives.end(),
228 broadcastHandle->dbch_hdevnotify);
229 // Emit for removable USB drives we were registered for.
230 if (it != m_removableDrives.end()) {
231 emit driveRemoved(pathFromEntry(*it));
232 stopDeviceNotification(*it);
233 m_removableDrives.erase(it);
234 }
235 }
236 break;
237 case DBT_DEVTYP_VOLUME: {
238 const DEV_BROADCAST_VOLUME *broadcastVolume = reinterpret_cast<const DEV_BROADCAST_VOLUME *>(broadcastHeader);
239 // WM_DEVICECHANGE/DBT_DEVTYP_VOLUME messages are sent to all toplevel windows. Compare a hash value to ensure
240 // it is handled only once.
241 const quintptr newHash = reinterpret_cast<quintptr>(broadcastVolume) + msg->wParam
242 + quintptr(broadcastVolume->dbcv_flags) + quintptr(broadcastVolume->dbcv_unitmask);
243 if (newHash == m_lastMessageHash)
244 return;
245 m_lastMessageHash = newHash;
246 // Check for DBTF_MEDIA (inserted/Removed Optical drives). Ignore for now.
247 if (broadcastVolume->dbcv_flags & DBTF_MEDIA)
248 return;
249 // Continue with plugged in USB media where dbcv_flags=0.
250 switch (msg->wParam) {
251 case DBT_DEVICEARRIVAL:
252 emit driveAdded();
253 break;
254 case DBT_DEVICEREMOVECOMPLETE: // See above for handling of drives registered with watchers
255 emit driveRemoved();
256 break;
257 }
258 }
259 break;
260 }
261}
262
263#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
264bool QWindowsRemovableDriveListener::nativeEventFilter(const QByteArray &, void *messageIn, qintptr *)
265#else
266bool QWindowsRemovableDriveListener::nativeEventFilter(const QByteArray &, void *messageIn, long *)
267#endif
268{
269 const MSG *msg = reinterpret_cast<const MSG *>(messageIn);
270 if (msg->message == WM_DEVICECHANGE) {
271 switch (msg->wParam) {
272 case DBT_CUSTOMEVENT:
273 handleDbtCustomEvent(msg);
274 break;
275 case DBT_DEVICEARRIVAL:
276 case DBT_DEVICEREMOVECOMPLETE:
277 handleDbtDriveArrivalRemoval(msg);
278 break;
279 }
280 }
281 return false;
282}
283
284// Set up listening for WM_DEVICECHANGE+DBT_CUSTOMEVENT for a removable drive path,
285void QWindowsRemovableDriveListener::addPath(const QString &p)
286{
287 const wchar_t drive = p.size() >= 2 && p.at(0).isLetter() && p.at(1) == QLatin1Char(':')
288 ? wchar_t(p.at(0).toUpper().unicode()) : L'\0';
289 if (!drive)
290 return;
291 // Already listening?
292 if (std::any_of(m_removableDrives.cbegin(), m_removableDrives.cend(),
293 [drive](const RemovableDriveEntry &e) { return e.drive == drive; })) {
294 return;
295 }
296
297 wchar_t devicePath[8] = L"\\\\.\\A:\\";
298 devicePath[4] = drive;
299 RemovableDriveEntry re;
300 re.drive = drive;
301 if (GetDriveTypeW(devicePath + 4) != DRIVE_REMOVABLE)
302 return;
303 const HANDLE volumeHandle =
304 CreateFile(devicePath, FILE_READ_ATTRIBUTES,
305 FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, 0,
306 OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, // Volume requires BACKUP_SEMANTICS
307 0);
308 if (volumeHandle == INVALID_HANDLE_VALUE) {
309 qErrnoWarning("CreateFile %ls failed.", devicePath);
310 return;
311 }
312
313 DEV_BROADCAST_HANDLE notify;
314 ZeroMemory(&notify, sizeof(notify));
315 notify.dbch_size = sizeof(notify);
316 notify.dbch_devicetype = DBT_DEVTYP_HANDLE;
317 notify.dbch_handle = volumeHandle;
318 QThreadData *currentData = QThreadData::current();
319 QEventDispatcherWin32 *winEventDispatcher = static_cast<QEventDispatcherWin32 *>(currentData->ensureEventDispatcher());
320 re.devNotify = RegisterDeviceNotification(winEventDispatcher->internalHwnd(),
321 &notify, DEVICE_NOTIFY_WINDOW_HANDLE);
322 // Empirically found: The notifications also work when the handle is immediately
323 // closed. Do it here to avoid having to close/reopen in lock message handling.
324 CloseHandle(volumeHandle);
325 if (!re.devNotify) {
326 qErrnoWarning("RegisterDeviceNotification %ls failed.", devicePath);
327 return;
328 }
329
330 m_removableDrives.push_back(re);
331}
332#endif // !Q_OS_WINRT
333
334///////////
335// QWindowsFileSystemWatcherEngine
336///////////
337QWindowsFileSystemWatcherEngine::Handle::Handle()
338 : handle(INVALID_HANDLE_VALUE), flags(0u)
339{
340}
341
342QWindowsFileSystemWatcherEngine::QWindowsFileSystemWatcherEngine(QObject *parent)
343 : QFileSystemWatcherEngine(parent)
344{
345#ifndef Q_OS_WINRT
346 if (QAbstractEventDispatcher *eventDispatcher = QAbstractEventDispatcher::instance()) {
347 m_driveListener = new QWindowsRemovableDriveListener(this);
348 eventDispatcher->installNativeEventFilter(m_driveListener);
349 parent->setProperty("_q_driveListener",
350 QVariant::fromValue(static_cast<QObject *>(m_driveListener)));
351 QObject::connect(m_driveListener, &QWindowsRemovableDriveListener::driveLockForRemoval,
352 this, &QWindowsFileSystemWatcherEngine::driveLockForRemoval);
353 QObject::connect(m_driveListener, &QWindowsRemovableDriveListener::driveLockForRemovalFailed,
354 this, &QWindowsFileSystemWatcherEngine::driveLockForRemovalFailed);
355 QObject::connect(m_driveListener,
356 QOverload<const QString &>::of(&QWindowsRemovableDriveListener::driveRemoved),
357 this, &QWindowsFileSystemWatcherEngine::driveRemoved);
358 } else {
359 qWarning("QFileSystemWatcher: Removable drive notification will not work"
360 " if there is no QCoreApplication instance.");
361 }
362#endif // !Q_OS_WINRT
363}
364
365QWindowsFileSystemWatcherEngine::~QWindowsFileSystemWatcherEngine()
366{
367 for (auto *thread : qAsConst(threads))
368 thread->stop();
369 for (auto *thread : qAsConst(threads))
370 thread->wait();
371 qDeleteAll(threads);
372}
373
374QStringList QWindowsFileSystemWatcherEngine::addPaths(const QStringList &paths,
375 QStringList *files,
376 QStringList *directories)
377{
378 DEBUG() << "Adding" << paths.count() << "to existing" << (files->count() + directories->count()) << "watchers";
379 QStringList unhandled;
380 for (const QString &path : paths) {
381 auto sg = qScopeGuard([&] { unhandled.push_back(path); });
382 QString normalPath = path;
383 if ((normalPath.endsWith(QLatin1Char('/')) && !normalPath.endsWith(QLatin1String(":/")))
384 || (normalPath.endsWith(QLatin1Char('\\')) && !normalPath.endsWith(QLatin1String(":\\")))) {
385 normalPath.chop(1);
386 }
387 QFileInfo fileInfo(normalPath);
388 if (!fileInfo.exists())
389 continue;
390
391 bool isDir = fileInfo.isDir();
392 if (isDir) {
393 if (directories->contains(path))
394 continue;
395 } else {
396 if (files->contains(path))
397 continue;
398 }
399
400 DEBUG() << "Looking for a thread/handle for" << normalPath;
401
402 const QString absolutePath = isDir ? fileInfo.absoluteFilePath() : fileInfo.absolutePath();
403 const uint flags = isDir
404 ? (FILE_NOTIFY_CHANGE_DIR_NAME
405 | FILE_NOTIFY_CHANGE_FILE_NAME)
406 : (FILE_NOTIFY_CHANGE_DIR_NAME
407 | FILE_NOTIFY_CHANGE_FILE_NAME
408 | FILE_NOTIFY_CHANGE_ATTRIBUTES
409 | FILE_NOTIFY_CHANGE_SIZE
410 | FILE_NOTIFY_CHANGE_LAST_WRITE
411 | FILE_NOTIFY_CHANGE_SECURITY);
412
413 QWindowsFileSystemWatcherEngine::PathInfo pathInfo;
414 pathInfo.absolutePath = absolutePath;
415 pathInfo.isDir = isDir;
416 pathInfo.path = path;
417 pathInfo = fileInfo;
418
419 // Look for a thread
420 QWindowsFileSystemWatcherEngineThread *thread = 0;
421 QWindowsFileSystemWatcherEngine::Handle handle;
422 QList<QWindowsFileSystemWatcherEngineThread *>::const_iterator jt, end;
423 end = threads.constEnd();
424 for(jt = threads.constBegin(); jt != end; ++jt) {
425 thread = *jt;
426 QMutexLocker locker(&(thread->mutex));
427
428 const auto hit = thread->handleForDir.find(QFileSystemWatcherPathKey(absolutePath));
429 if (hit != thread->handleForDir.end() && hit.value().flags < flags) {
430 // Requesting to add a file whose directory has been added previously.
431 // Recreate the notification handle to add the missing notification attributes
432 // for files (FILE_NOTIFY_CHANGE_ATTRIBUTES...)
433 DEBUG() << "recreating" << absolutePath << Qt::hex << Qt::showbase << hit.value().flags
434 << "->" << flags;
435 const Qt::HANDLE fileHandle = createChangeNotification(absolutePath, flags);
436 if (fileHandle != INVALID_HANDLE_VALUE) {
437 const int index = thread->handles.indexOf(hit.value().handle);
438 const auto pit = thread->pathInfoForHandle.find(hit.value().handle);
439 Q_ASSERT(index != -1);
440 Q_ASSERT(pit != thread->pathInfoForHandle.end());
441 FindCloseChangeNotification(hit.value().handle);
442 thread->handles[index] = hit.value().handle = fileHandle;
443 hit.value().flags = flags;
444 thread->pathInfoForHandle.insert(fileHandle, pit.value());
445 thread->pathInfoForHandle.erase(pit);
446 }
447 }
448 // In addition, check on flags for sufficient notification attributes
449 if (hit != thread->handleForDir.end() && hit.value().flags >= flags) {
450 handle = hit.value();
451 // found a thread now insert...
452 DEBUG() << "Found a thread" << thread;
453
454 QWindowsFileSystemWatcherEngineThread::PathInfoHash &h =
455 thread->pathInfoForHandle[handle.handle];
456 const QFileSystemWatcherPathKey key(fileInfo.absoluteFilePath());
457 if (!h.contains(key)) {
458 thread->pathInfoForHandle[handle.handle].insert(key, pathInfo);
459 if (isDir)
460 directories->append(path);
461 else
462 files->append(path);
463 }
464 sg.dismiss();
465 thread->wakeup();
466 break;
467 }
468 }
469
470 // no thread found, first create a handle
471 if (handle.handle == INVALID_HANDLE_VALUE) {
472 DEBUG() << "No thread found";
473 handle.handle = createChangeNotification(absolutePath, flags);
474 handle.flags = flags;
475 if (handle.handle == INVALID_HANDLE_VALUE)
476 continue;
477
478 // now look for a thread to insert
479 bool found = false;
480 for (QWindowsFileSystemWatcherEngineThread *thread : qAsConst(threads)) {
481 QMutexLocker locker(&(thread->mutex));
482 if (thread->handles.count() < MAXIMUM_WAIT_OBJECTS) {
483 DEBUG() << "Added handle" << handle.handle << "for" << absolutePath << "to watch" << fileInfo.absoluteFilePath()
484 << "to existing thread " << thread;
485 thread->handles.append(handle.handle);
486 thread->handleForDir.insert(QFileSystemWatcherPathKey(absolutePath), handle);
487
488 thread->pathInfoForHandle[handle.handle].insert(QFileSystemWatcherPathKey(fileInfo.absoluteFilePath()), pathInfo);
489 if (isDir)
490 directories->append(path);
491 else
492 files->append(path);
493
494 sg.dismiss();
495 found = true;
496 thread->wakeup();
497 break;
498 }
499 }
500 if (!found) {
501 QWindowsFileSystemWatcherEngineThread *thread = new QWindowsFileSystemWatcherEngineThread();
502 DEBUG() << " ###Creating new thread" << thread << '(' << (threads.count()+1) << "threads)";
503 thread->handles.append(handle.handle);
504 thread->handleForDir.insert(QFileSystemWatcherPathKey(absolutePath), handle);
505
506 thread->pathInfoForHandle[handle.handle].insert(QFileSystemWatcherPathKey(fileInfo.absoluteFilePath()), pathInfo);
507 if (isDir)
508 directories->append(path);
509 else
510 files->append(path);
511
512 connect(thread, SIGNAL(fileChanged(QString,bool)),
513 this, SIGNAL(fileChanged(QString,bool)));
514 connect(thread, SIGNAL(directoryChanged(QString,bool)),
515 this, SIGNAL(directoryChanged(QString,bool)));
516
517 thread->msg = '@';
518 thread->start();
519 threads.append(thread);
520 sg.dismiss();
521 }
522 }
523 }
524
525#ifndef Q_OS_WINRT
526 if (Q_LIKELY(m_driveListener)) {
527 for (const QString &path : paths) {
528 if (!unhandled.contains(path))
529 m_driveListener->addPath(path);
530 }
531 }
532#endif // !Q_OS_WINRT
533 return unhandled;
534}
535
536QStringList QWindowsFileSystemWatcherEngine::removePaths(const QStringList &paths,
537 QStringList *files,
538 QStringList *directories)
539{
540 DEBUG() << "removePaths" << paths;
541 QStringList unhandled;
542 for (const QString &path : paths) {
543 auto sg = qScopeGuard([&] { unhandled.push_back(path); });
544 QString normalPath = path;
545 if (normalPath.endsWith(QLatin1Char('/')) || normalPath.endsWith(QLatin1Char('\\')))
546 normalPath.chop(1);
547 QFileInfo fileInfo(normalPath);
548 DEBUG() << "removing" << normalPath;
549 QString absolutePath = fileInfo.absoluteFilePath();
550 QList<QWindowsFileSystemWatcherEngineThread *>::iterator jt, end;
551 end = threads.end();
552 for(jt = threads.begin(); jt!= end; ++jt) {
553 QWindowsFileSystemWatcherEngineThread *thread = *jt;
554 if (*jt == 0)
555 continue;
556
557 QMutexLocker locker(&(thread->mutex));
558
559 QWindowsFileSystemWatcherEngine::Handle handle = thread->handleForDir.value(QFileSystemWatcherPathKey(absolutePath));
560 if (handle.handle == INVALID_HANDLE_VALUE) {
561 // perhaps path is a file?
562 absolutePath = fileInfo.absolutePath();
563 handle = thread->handleForDir.value(QFileSystemWatcherPathKey(absolutePath));
564 }
565 if (handle.handle != INVALID_HANDLE_VALUE) {
566 QWindowsFileSystemWatcherEngineThread::PathInfoHash &h =
567 thread->pathInfoForHandle[handle.handle];
568 if (h.remove(QFileSystemWatcherPathKey(fileInfo.absoluteFilePath()))) {
569 // ###
570 files->removeAll(path);
571 directories->removeAll(path);
572 sg.dismiss();
573
574 if (h.isEmpty()) {
575 DEBUG() << "Closing handle" << handle.handle;
576 FindCloseChangeNotification(handle.handle); // This one might generate a notification
577
578 int indexOfHandle = thread->handles.indexOf(handle.handle);
579 Q_ASSERT(indexOfHandle != -1);
580 thread->handles.remove(indexOfHandle);
581
582 thread->handleForDir.remove(QFileSystemWatcherPathKey(absolutePath));
583 // h is now invalid
584
585 if (thread->handleForDir.isEmpty()) {
586 DEBUG() << "Stopping thread " << thread;
587 locker.unlock();
588 thread->stop();
589 thread->wait();
590 locker.relock();
591 // We can't delete the thread until the mutex locker is
592 // out of scope
593 }
594 }
595 }
596 // Found the file, go to next one
597 break;
598 }
599 }
600 }
601
602 // Remove all threads that we stopped
603 QList<QWindowsFileSystemWatcherEngineThread *>::iterator jt, end;
604 end = threads.end();
605 for(jt = threads.begin(); jt != end; ++jt) {
606 if (!(*jt)->isRunning()) {
607 delete *jt;
608 *jt = 0;
609 }
610 }
611
612 threads.removeAll(0);
613 return unhandled;
614}
615
616///////////
617// QWindowsFileSystemWatcherEngineThread
618///////////
619
620QWindowsFileSystemWatcherEngineThread::QWindowsFileSystemWatcherEngineThread()
621 : msg(0)
622{
623 if (HANDLE h = CreateEvent(0, false, false, 0)) {
624 handles.reserve(MAXIMUM_WAIT_OBJECTS);
625 handles.append(h);
626 }
627}
628
629
630QWindowsFileSystemWatcherEngineThread::~QWindowsFileSystemWatcherEngineThread()
631{
632 CloseHandle(handles.at(0));
633 handles[0] = INVALID_HANDLE_VALUE;
634
635 for (HANDLE h : qAsConst(handles)) {
636 if (h == INVALID_HANDLE_VALUE)
637 continue;
638 FindCloseChangeNotification(h);
639 }
640}
641
642Q_DECL_COLD_FUNCTION
643static QString msgFindNextFailed(const QWindowsFileSystemWatcherEngineThread::PathInfoHash &pathInfos)
644{
645 QString str;
646 str += QLatin1String("QFileSystemWatcher: FindNextChangeNotification failed for");
647 for (const QWindowsFileSystemWatcherEngine::PathInfo &pathInfo : pathInfos)
648 str += QLatin1String(" \"") + QDir::toNativeSeparators(pathInfo.absolutePath) + QLatin1Char('"');
649 str += QLatin1Char(' ');
650 return str;
651}
652
653void QWindowsFileSystemWatcherEngineThread::run()
654{
655 QMutexLocker locker(&mutex);
656 forever {
657 QVector<HANDLE> handlesCopy = handles;
658 locker.unlock();
659 DEBUG() << "QWindowsFileSystemWatcherThread" << this << "waiting on" << handlesCopy.count() << "handles";
660 DWORD r = WaitForMultipleObjects(handlesCopy.count(), handlesCopy.constData(), false, INFINITE);
661 locker.relock();
662 do {
663 if (r == WAIT_OBJECT_0) {
664 int m = msg;
665 msg = 0;
666 if (m == 'q') {
667 DEBUG() << "thread" << this << "told to quit";
668 return;
669 }
670 if (m != '@')
671 DEBUG() << "QWindowsFileSystemWatcherEngine: unknown message sent to thread: " << char(m);
672 break;
673 }
674 if (r > WAIT_OBJECT_0 && r < WAIT_OBJECT_0 + uint(handlesCopy.count())) {
675 int at = r - WAIT_OBJECT_0;
676 Q_ASSERT(at < handlesCopy.count());
677 HANDLE handle = handlesCopy.at(at);
678
679 // When removing a path, FindCloseChangeNotification might actually fire a notification
680 // for some reason, so we must check if the handle exist in the handles vector
681 if (handles.contains(handle)) {
682 DEBUG() << "thread" << this << "Acknowledged handle:" << at << handle;
683 QWindowsFileSystemWatcherEngineThread::PathInfoHash &h = pathInfoForHandle[handle];
684 bool fakeRemove = false;
685
686 if (!FindNextChangeNotification(handle)) {
687 const DWORD error = GetLastError();
688
689 if (error == ERROR_ACCESS_DENIED) {
690 // for directories, our object's handle appears to be woken up when the target of a
691 // watch is deleted, before the watched thing is actually deleted...
692 // anyway.. we're given an error code of ERROR_ACCESS_DENIED in that case.
693 fakeRemove = true;
694 }
695
696 qErrnoWarning(error, "%ls", qUtf16Printable(msgFindNextFailed(h)));
697 }
698 for (auto it = h.begin(), end = h.end(); it != end; /*erasing*/ ) {
699 auto x = it++;
700 QString absolutePath = x.value().absolutePath;
701 QFileInfo fileInfo(x.value().path);
702 DEBUG() << "checking" << x.key();
703
704 // i'm not completely sure the fileInfo.exist() check will ever work... see QTBUG-2331
705 // ..however, I'm not completely sure enough to remove it.
706 if (fakeRemove || !fileInfo.exists()) {
707 DEBUG() << x.key() << "removed!";
708 if (x.value().isDir)
709 emit directoryChanged(x.value().path, true);
710 else
711 emit fileChanged(x.value().path, true);
712 h.erase(x);
713
714 // close the notification handle if the directory has been removed
715 if (h.isEmpty()) {
716 DEBUG() << "Thread closing handle" << handle;
717 FindCloseChangeNotification(handle); // This one might generate a notification
718
719 int indexOfHandle = handles.indexOf(handle);
720 Q_ASSERT(indexOfHandle != -1);
721 handles.remove(indexOfHandle);
722
723 handleForDir.remove(QFileSystemWatcherPathKey(absolutePath));
724 // h is now invalid
725 break;
726 }
727 } else if (x.value().isDir) {
728 DEBUG() << x.key() << "directory changed!";
729 emit directoryChanged(x.value().path, false);
730 x.value() = fileInfo;
731 } else if (x.value() != fileInfo) {
732 DEBUG() << x.key() << "file changed!";
733 emit fileChanged(x.value().path, false);
734 x.value() = fileInfo;
735 }
736 }
737 }
738 } else {
739 // qErrnoWarning("QFileSystemWatcher: error while waiting for change notification");
740 break; // avoid endless loop
741 }
742 handlesCopy = handles;
743 r = WaitForMultipleObjects(handlesCopy.count(), handlesCopy.constData(), false, 0);
744 } while (r != WAIT_TIMEOUT);
745 }
746}
747
748
749void QWindowsFileSystemWatcherEngineThread::stop()
750{
751 msg = 'q';
752 SetEvent(handles.at(0));
753}
754
755void QWindowsFileSystemWatcherEngineThread::wakeup()
756{
757 msg = '@';
758 SetEvent(handles.at(0));
759}
760
761QT_END_NAMESPACE
762
763#ifndef Q_OS_WINRT
764# include "qfilesystemwatcher_win.moc"
765#endif
766

Warning: That file was not part of the compilation database. It may have many parsing errors.