DirectoryWatcher.cpp 13 KB

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