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