1//
2// DirectoryWatcher.cpp
3//
4// Library: Foundation
5// Package: Filesystem
6// Module: DirectoryWatcher
7//
8// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
9// and Contributors.
10//
11// SPDX-License-Identifier: BSL-1.0
12//
13
14
15#include "Poco/DirectoryWatcher.h"
16#ifdef POCO_OS_FAMILY_WINDOWS
17#include "Poco/UnWindows.h"
18#endif
19
20
21#ifndef POCO_NO_INOTIFY
22
23
24#include "Poco/Path.h"
25#include "Poco/Glob.h"
26#include "Poco/DirectoryIterator.h"
27#include "Poco/Event.h"
28#include "Poco/Exception.h"
29#include "Poco/Buffer.h"
30#if POCO_OS == POCO_OS_LINUX
31 #include <sys/inotify.h>
32 #include <sys/select.h>
33 #include <unistd.h>
34#elif POCO_OS == POCO_OS_MAC_OS_X || POCO_OS == POCO_OS_FREE_BSD
35 #include <fcntl.h>
36 #include <sys/types.h>
37 #include <sys/event.h>
38 #include <sys/time.h>
39 #include <unistd.h>
40 #if (POCO_OS == POCO_OS_FREE_BSD) && !defined(O_EVTONLY)
41 #define O_EVTONLY 0x8000
42 #endif
43#endif
44#include <algorithm>
45#include <map>
46
47
48namespace Poco {
49
50
51class DirectoryWatcherStrategy
52{
53public:
54 DirectoryWatcherStrategy(DirectoryWatcher& ownerWatcher):
55 _owner(ownerWatcher)
56 {
57 }
58
59 virtual ~DirectoryWatcherStrategy()
60 {
61 }
62
63 DirectoryWatcher& owner()
64 {
65 return _owner;
66 }
67
68 virtual void run() = 0;
69 virtual void stop() = 0;
70 virtual bool supportsMoveEvents() const = 0;
71
72protected:
73 struct ItemInfo
74 {
75 ItemInfo():
76 size(0)
77 {
78 }
79
80 ItemInfo(const ItemInfo& other):
81 path(other.path),
82 size(other.size),
83 lastModified(other.lastModified)
84 {
85 }
86
87 explicit ItemInfo(const File& f):
88 path(f.path()),
89 size(f.isFile() ? f.getSize() : 0),
90 lastModified(f.getLastModified())
91 {
92 }
93
94 std::string path;
95 File::FileSize size;
96 Timestamp lastModified;
97 };
98 typedef std::map<std::string, ItemInfo> ItemInfoMap;
99
100 void scan(ItemInfoMap& entries)
101 {
102 DirectoryIterator it(owner().directory());
103 DirectoryIterator end;
104 while (it != end)
105 {
106 // DirectoryWatcher should not stop watching if it fails to get the info
107 // of a file, it should just ignore it.
108 try
109 {
110 entries[it.path().getFileName()] = ItemInfo(*it);
111 }
112 catch (Poco::FileNotFoundException&)
113 {
114 // The file is missing, remove it from the entries so it is treated as
115 // deleted.
116 entries.erase(it.path().getFileName());
117 }
118 ++it;
119 }
120 }
121
122 void compare(ItemInfoMap& oldEntries, ItemInfoMap& newEntries)
123 {
124 for (ItemInfoMap::iterator itn = newEntries.begin(); itn != newEntries.end(); ++itn)
125 {
126 ItemInfoMap::iterator ito = oldEntries.find(itn->first);
127 if (ito != oldEntries.end())
128 {
129 if ((owner().eventMask() & DirectoryWatcher::DW_ITEM_MODIFIED) && !owner().eventsSuspended())
130 {
131 if (itn->second.size != ito->second.size || itn->second.lastModified != ito->second.lastModified)
132 {
133 Poco::File f(itn->second.path);
134 DirectoryWatcher::DirectoryEvent ev(f, DirectoryWatcher::DW_ITEM_MODIFIED);
135 owner().itemModified(&owner(), ev);
136 }
137 }
138 oldEntries.erase(ito);
139 }
140 else if ((owner().eventMask() & DirectoryWatcher::DW_ITEM_ADDED) && !owner().eventsSuspended())
141 {
142 Poco::File f(itn->second.path);
143 DirectoryWatcher::DirectoryEvent ev(f, DirectoryWatcher::DW_ITEM_ADDED);
144 owner().itemAdded(&owner(), ev);
145 }
146 }
147 if ((owner().eventMask() & DirectoryWatcher::DW_ITEM_REMOVED) && !owner().eventsSuspended())
148 {
149 for (ItemInfoMap::iterator it = oldEntries.begin(); it != oldEntries.end(); ++it)
150 {
151 Poco::File f(it->second.path);
152 DirectoryWatcher::DirectoryEvent ev(f, DirectoryWatcher::DW_ITEM_REMOVED);
153 owner().itemRemoved(&owner(), ev);
154 }
155 }
156 }
157
158private:
159 DirectoryWatcherStrategy();
160 DirectoryWatcherStrategy(const DirectoryWatcherStrategy&);
161 DirectoryWatcherStrategy& operator = (const DirectoryWatcherStrategy&);
162
163 DirectoryWatcher& _owner;
164};
165
166
167#if POCO_OS == POCO_OS_WINDOWS_NT
168
169
170class WindowsDirectoryWatcherStrategy: public DirectoryWatcherStrategy
171{
172public:
173 WindowsDirectoryWatcherStrategy(DirectoryWatcher& owner):
174 DirectoryWatcherStrategy(owner)
175 {
176 _hStopped = CreateEventW(NULL, FALSE, FALSE, NULL);
177 if (!_hStopped)
178 throw SystemException("cannot create event");
179 }
180
181 ~WindowsDirectoryWatcherStrategy()
182 {
183 CloseHandle(_hStopped);
184 }
185
186 void run()
187 {
188 ItemInfoMap entries;
189 scan(entries);
190
191 DWORD filter = FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME;
192 if (owner().eventMask() & DirectoryWatcher::DW_ITEM_MODIFIED)
193 filter |= FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE;
194
195 std::string path(owner().directory().path());
196 std::wstring upath;
197 FileImpl::convertPath(path.c_str(), upath);
198 HANDLE hChange = FindFirstChangeNotificationW(upath.c_str(), FALSE, filter);
199
200 if (hChange == INVALID_HANDLE_VALUE)
201 {
202 try
203 {
204 FileImpl::handleLastErrorImpl(path);
205 }
206 catch (Poco::Exception& exc)
207 {
208 owner().scanError(&owner(), exc);
209 }
210 return;
211 }
212
213 bool stopped = false;
214 while (!stopped)
215 {
216 try
217 {
218 HANDLE h[2];
219 h[0] = _hStopped;
220 h[1] = hChange;
221 switch (WaitForMultipleObjects(2, h, FALSE, INFINITE))
222 {
223 case WAIT_OBJECT_0:
224 stopped = true;
225 break;
226 case WAIT_OBJECT_0 + 1:
227 {
228 ItemInfoMap newEntries;
229 scan(newEntries);
230 compare(entries, newEntries);
231 std::swap(entries, newEntries);
232 if (FindNextChangeNotification(hChange) == FALSE)
233 {
234 FileImpl::handleLastErrorImpl(path);
235 }
236 }
237 break;
238 default:
239 throw SystemException("failed to wait for directory changes");
240 }
241 }
242 catch (Poco::Exception& exc)
243 {
244 owner().scanError(&owner(), exc);
245 }
246 }
247 FindCloseChangeNotification(hChange);
248 }
249
250 void stop()
251 {
252 SetEvent(_hStopped);
253 }
254
255 bool supportsMoveEvents() const
256 {
257 return false;
258 }
259
260private:
261 HANDLE _hStopped;
262};
263
264
265#elif POCO_OS == POCO_OS_LINUX
266
267
268class LinuxDirectoryWatcherStrategy: public DirectoryWatcherStrategy
269{
270public:
271 LinuxDirectoryWatcherStrategy(DirectoryWatcher& ownerWatcher):
272 DirectoryWatcherStrategy(ownerWatcher),
273 _fd(-1),
274 _stopped(false)
275 {
276 _fd = inotify_init();
277 if (_fd == -1) throw Poco::IOException("cannot initialize inotify", errno);
278 }
279
280 ~LinuxDirectoryWatcherStrategy()
281 {
282 close(_fd);
283 }
284
285 void run()
286 {
287 int mask = 0;
288 if (owner().eventMask() & DirectoryWatcher::DW_ITEM_ADDED)
289 mask |= IN_CREATE;
290 if (owner().eventMask() & DirectoryWatcher::DW_ITEM_REMOVED)
291 mask |= IN_DELETE;
292 if (owner().eventMask() & DirectoryWatcher::DW_ITEM_MODIFIED)
293 mask |= IN_MODIFY;
294 if (owner().eventMask() & DirectoryWatcher::DW_ITEM_MOVED_FROM)
295 mask |= IN_MOVED_FROM;
296 if (owner().eventMask() & DirectoryWatcher::DW_ITEM_MOVED_TO)
297 mask |= IN_MOVED_TO;
298 int wd = inotify_add_watch(_fd, owner().directory().path().c_str(), mask);
299 if (wd == -1)
300 {
301 try
302 {
303 FileImpl::handleLastErrorImpl(owner().directory().path());
304 }
305 catch (Poco::Exception& exc)
306 {
307 owner().scanError(&owner(), exc);
308 }
309 }
310
311 Poco::Buffer<char> buffer(4096);
312 while (!_stopped)
313 {
314 fd_set fds;
315 FD_ZERO(&fds);
316 FD_SET(_fd, &fds);
317
318 struct timeval tv;
319 tv.tv_sec = 0;
320 tv.tv_usec = 200000;
321
322 if (select(_fd + 1, &fds, NULL, NULL, &tv) == 1)
323 {
324 int n = read(_fd, buffer.begin(), buffer.size());
325 int i = 0;
326 if (n > 0)
327 {
328 while (n > 0)
329 {
330 struct inotify_event* pEvent = reinterpret_cast<struct inotify_event*>(buffer.begin() + i);
331
332 if (pEvent->len > 0)
333 {
334 if (!owner().eventsSuspended())
335 {
336 Poco::Path p(owner().directory().path());
337 p.makeDirectory();
338 p.setFileName(pEvent->name);
339 Poco::File f(p.toString());
340
341 if ((pEvent->mask & IN_CREATE) && (owner().eventMask() & DirectoryWatcher::DW_ITEM_ADDED))
342 {
343 DirectoryWatcher::DirectoryEvent ev(f, DirectoryWatcher::DW_ITEM_ADDED);
344 owner().itemAdded(&owner(), ev);
345 }
346 if ((pEvent->mask & IN_DELETE) && (owner().eventMask() & DirectoryWatcher::DW_ITEM_REMOVED))
347 {
348 DirectoryWatcher::DirectoryEvent ev(f, DirectoryWatcher::DW_ITEM_REMOVED);
349 owner().itemRemoved(&owner(), ev);
350 }
351 if ((pEvent->mask & IN_MODIFY) && (owner().eventMask() & DirectoryWatcher::DW_ITEM_MODIFIED))
352 {
353 DirectoryWatcher::DirectoryEvent ev(f, DirectoryWatcher::DW_ITEM_MODIFIED);
354 owner().itemModified(&owner(), ev);
355 }
356 if ((pEvent->mask & IN_MOVED_FROM) && (owner().eventMask() & DirectoryWatcher::DW_ITEM_MOVED_FROM))
357 {
358 DirectoryWatcher::DirectoryEvent ev(f, DirectoryWatcher::DW_ITEM_MOVED_FROM);
359 owner().itemMovedFrom(&owner(), ev);
360 }
361 if ((pEvent->mask & IN_MOVED_TO) && (owner().eventMask() & DirectoryWatcher::DW_ITEM_MOVED_TO))
362 {
363 DirectoryWatcher::DirectoryEvent ev(f, DirectoryWatcher::DW_ITEM_MOVED_TO);
364 owner().itemMovedTo(&owner(), ev);
365 }
366 }
367 }
368
369 i += sizeof(inotify_event) + pEvent->len;
370 n -= sizeof(inotify_event) + pEvent->len;
371 }
372 }
373 }
374 }
375 }
376
377 void stop()
378 {
379 _stopped = true;
380 }
381
382 bool supportsMoveEvents() const
383 {
384 return true;
385 }
386
387private:
388 int _fd;
389 bool _stopped;
390};
391
392
393#elif POCO_OS == POCO_OS_MAC_OS_X || POCO_OS == POCO_OS_FREE_BSD
394
395
396class BSDDirectoryWatcherStrategy: public DirectoryWatcherStrategy
397{
398public:
399 BSDDirectoryWatcherStrategy(DirectoryWatcher& owner):
400 DirectoryWatcherStrategy(owner),
401 _queueFD(-1),
402 _dirFD(-1),
403 _stopped(false)
404 {
405 _dirFD = open(owner.directory().path().c_str(), O_EVTONLY);
406 if (_dirFD < 0) throw Poco::FileNotFoundException(owner.directory().path());
407 _queueFD = kqueue();
408 if (_queueFD < 0)
409 {
410 close(_dirFD);
411 throw Poco::SystemException("Cannot create kqueue", errno);
412 }
413 }
414
415 ~BSDDirectoryWatcherStrategy()
416 {
417 close(_dirFD);
418 close(_queueFD);
419 }
420
421 void run()
422 {
423 Poco::Timestamp lastScan;
424 ItemInfoMap entries;
425 scan(entries);
426
427 while (!_stopped)
428 {
429 struct timespec timeout;
430 timeout.tv_sec = 0;
431 timeout.tv_nsec = 200000000;
432 unsigned eventFilter = NOTE_WRITE;
433 struct kevent event;
434 struct kevent eventData;
435 EV_SET(&event, _dirFD, EVFILT_VNODE, EV_ADD | EV_CLEAR, eventFilter, 0, 0);
436 int nEvents = kevent(_queueFD, &event, 1, &eventData, 1, &timeout);
437 if (nEvents < 0 || eventData.flags == EV_ERROR)
438 {
439 try
440 {
441 FileImpl::handleLastErrorImpl(owner().directory().path());
442 }
443 catch (Poco::Exception& exc)
444 {
445 owner().scanError(&owner(), exc);
446 }
447 }
448 else if (nEvents > 0 || ((owner().eventMask() & DirectoryWatcher::DW_ITEM_MODIFIED) && lastScan.isElapsed(owner().scanInterval()*1000000)))
449 {
450 ItemInfoMap newEntries;
451 scan(newEntries);
452 compare(entries, newEntries);
453 std::swap(entries, newEntries);
454 lastScan.update();
455 }
456 }
457 }
458
459 void stop()
460 {
461 _stopped = true;
462 }
463
464 bool supportsMoveEvents() const
465 {
466 return false;
467 }
468
469private:
470 int _queueFD;
471 int _dirFD;
472 bool _stopped;
473};
474
475
476#endif
477
478
479class PollingDirectoryWatcherStrategy: public DirectoryWatcherStrategy
480{
481public:
482 PollingDirectoryWatcherStrategy(DirectoryWatcher& ownerWatcher):
483 DirectoryWatcherStrategy(ownerWatcher)
484 {
485 }
486
487 ~PollingDirectoryWatcherStrategy()
488 {
489 }
490
491 void run()
492 {
493 ItemInfoMap entries;
494 scan(entries);
495 while (!_stopped.tryWait(1000*owner().scanInterval()))
496 {
497 try
498 {
499 ItemInfoMap newEntries;
500 scan(newEntries);
501 compare(entries, newEntries);
502 std::swap(entries, newEntries);
503 }
504 catch (Poco::Exception& exc)
505 {
506 owner().scanError(&owner(), exc);
507 }
508 }
509 }
510
511 void stop()
512 {
513 _stopped.set();
514 }
515
516 bool supportsMoveEvents() const
517 {
518 return false;
519 }
520
521private:
522 Poco::Event _stopped;
523};
524
525
526
527DirectoryWatcher::DirectoryWatcher(const std::string& path, int otherEventMask, int otherScanInterval,
528 bool forceScan) :
529 _directory(path),
530 _eventMask(otherEventMask),
531 _eventsSuspended(0),
532 _scanInterval(otherScanInterval),
533 _forceScan(forceScan)
534{
535 init();
536}
537
538
539DirectoryWatcher::DirectoryWatcher(const Poco::File& otherDirectory, int otherEventMask, int otherScanInterval,
540 bool forceScan) :
541 _directory(otherDirectory),
542 _eventMask(otherEventMask),
543 _eventsSuspended(0),
544 _scanInterval(otherScanInterval),
545 _forceScan(forceScan)
546{
547 init();
548}
549
550
551DirectoryWatcher::~DirectoryWatcher()
552{
553 try
554 {
555 stop();
556 delete _pStrategy;
557 }
558 catch (...)
559 {
560 poco_unexpected();
561 }
562}
563
564
565void DirectoryWatcher::suspendEvents()
566{
567 poco_assert (_eventsSuspended > 0);
568
569 _eventsSuspended--;
570}
571
572
573void DirectoryWatcher::resumeEvents()
574{
575 _eventsSuspended++;
576}
577
578
579void DirectoryWatcher::init()
580{
581 if (!_directory.exists())
582 throw Poco::FileNotFoundException(_directory.path());
583
584 if (!_directory.isDirectory())
585 throw Poco::InvalidArgumentException("not a directory", _directory.path());
586
587 if (!_forceScan)
588 {
589#if POCO_OS == POCO_OS_WINDOWS_NT
590 _pStrategy = new WindowsDirectoryWatcherStrategy(*this);
591#elif POCO_OS == POCO_OS_LINUX
592 _pStrategy = new LinuxDirectoryWatcherStrategy(*this);
593#elif POCO_OS == POCO_OS_MAC_OS_X || POCO_OS == POCO_OS_FREE_BSD
594 _pStrategy = new BSDDirectoryWatcherStrategy(*this);
595#else
596 _pStrategy = new PollingDirectoryWatcherStrategy(*this);
597#endif
598 }
599 else
600 {
601 _pStrategy = new PollingDirectoryWatcherStrategy(*this);
602 }
603
604 _thread.start(*this);
605}
606
607
608void DirectoryWatcher::run()
609{
610 _pStrategy->run();
611}
612
613
614void DirectoryWatcher::stop()
615{
616 _pStrategy->stop();
617 _thread.join();
618}
619
620
621bool DirectoryWatcher::supportsMoveEvents() const
622{
623 return _pStrategy->supportsMoveEvents();
624}
625
626
627} // namespace Poco
628
629
630#endif // POCO_NO_INOTIFY
631