MRPT  1.9.9
CFileSystemWatcher.cpp
Go to the documentation of this file.
1 /* +------------------------------------------------------------------------+
2  | Mobile Robot Programming Toolkit (MRPT) |
3  | https://www.mrpt.org/ |
4  | |
5  | Copyright (c) 2005-2020, Individual contributors, see AUTHORS file |
6  | See: https://www.mrpt.org/Authors - All rights reserved. |
7  | Released under BSD License. See: https://www.mrpt.org/License |
8  +------------------------------------------------------------------------+ */
9 
10 #include "system-precomp.h" // Precompiled headers
11 
12 #include <mrpt/config.h>
13 
14 #ifdef _WIN32
15 #ifndef _WIN32_WINNT
16 #define _WIN32_WINNT 0x0400
17 #endif
18 #include <windows.h>
19 #else
20 #if MRPT_HAS_INOTIFY
21 #include <sys/inotify.h>
22 #endif
23 
24 // #include <time.h>
25 #include <unistd.h>
26 #include <cerrno>
27 #include <cstdio>
28 #endif
29 
30 #include <mrpt/core/exceptions.h> // for THROW_EXCEPTION, ASSERT_
32 #include <mrpt/system/filesystem.h> // for directoryExists()
33 #include <cstring> // for NULL, memcpy
34 
35 using namespace mrpt::system;
36 using namespace std;
37 
38 /*---------------------------------------------------------------
39  Constructor
40  ---------------------------------------------------------------*/
42  : m_watchedDirectory(path)
43 {
45  ASSERT_(!path.empty());
46 
47  if (m_watchedDirectory[m_watchedDirectory.size() - 1] != '/' &&
48  m_watchedDirectory[m_watchedDirectory.size() - 1] != '\\')
49  m_watchedDirectory.push_back('/');
50 
51 #ifdef _WIN32
52  // Windows version:
53  HANDLE hDir = CreateFileA(
54  path.c_str(), FILE_LIST_DIRECTORY,
55  FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
56  nullptr, // security attributes
57  OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
58  if (hDir == INVALID_HANDLE_VALUE)
59  {
60  m_hNotif = nullptr;
61  THROW_EXCEPTION("FindFirstChangeNotificationA returned error!");
62  }
63 
64  m_hNotif = static_cast<void*>(hDir);
65 
67 
68 #else
69 #if MRPT_HAS_INOTIFY
70  // Linux version:
71  m_wd = -1;
72 
73  m_fd = inotify_init();
74  if (m_fd < 0) THROW_EXCEPTION("inotify_init returned error!");
75 
76  // Create watcher:
77  m_wd = inotify_add_watch(
78  m_fd, path.c_str(),
79  IN_CLOSE_WRITE | IN_DELETE | IN_MOVED_TO | IN_MOVED_FROM | IN_CREATE |
80  IN_ACCESS);
81 
82  if (m_wd < 0) THROW_EXCEPTION("inotify_add_watch returned error!");
83 #endif
84 #endif
85  MRPT_END
86 }
87 
88 /*---------------------------------------------------------------
89  Destructor
90  ---------------------------------------------------------------*/
92 {
93 #ifdef _WIN32
94  // Windows version:
95  if (m_hNotif)
96  {
97  // Kill thread:
98  CloseHandle(HANDLE(m_hNotif));
99  m_hNotif = nullptr;
100  }
101 #else
102 #if MRPT_HAS_INOTIFY
103  // Linux version:
104  if (m_fd >= 0)
105  {
106  close(m_fd);
107  m_fd = -1;
108  if (m_wd >= 0) inotify_rm_watch(m_fd, m_wd);
109  }
110 #endif
111 #endif
112 }
113 
114 /*---------------------------------------------------------------
115  getChanges
116  ---------------------------------------------------------------*/
118 {
119  out_list.clear();
120 
121 #ifdef _WIN32
122  // Windows version:
123  ASSERTMSG_(
124  m_hNotif != nullptr,
125  "CFileSystemWatcher was not initialized correctly.");
126 
127  // Work is done in thread_win32_watch().
128  // Just check for incoming mail:
129  while (!m_queue_events_win32_msgs.empty())
130  {
131  TFileSystemChange* obj = nullptr;
132  {
133  std::lock_guard<std::mutex> lock(m_queue_events_win32_cs);
134  if (!m_queue_events_win32_msgs.empty())
135  {
136  auto ret = m_queue_events_win32_msgs.front();
138  obj = ret;
139  }
140  }
141  if (obj)
142  {
143  out_list.push_back(*obj);
144  delete obj;
145  }
146  }
147 
148 #else
149 #if MRPT_HAS_INOTIFY
150  if (m_fd < 0) return; // Not open?
151 
152  // Linux version:
153  // Refer to:
154  // http://www.linuxjournal.com/article/8478
155  // http://inotify.aiken.cz/?section=common&page=home&lang=en
156  struct timeval time
157  {
158  };
159  fd_set rfds;
160  int ret;
161 
162  // timeout
163  time.tv_sec = 0;
164  time.tv_usec = 100;
165 
166  // zero-out the fd_set
167  FD_ZERO(&rfds);
168 
169  // Add inotify fd
170  FD_SET(m_fd, &rfds);
171 
172  ret = select(m_fd + 1, &rfds, nullptr, nullptr, &time);
173  if (ret < 0)
174  {
175  perror("[CFileSystemWatcher::getChanges] select");
176  return;
177  }
178 
179  else if (!ret)
180  {
181  // timed out!
182  }
183  else if (FD_ISSET(m_fd, &rfds))
184  {
185 // inotify events are available! : Read them!
186 
187 /* size of the event structure, not counting name */
188 #define EVENT_SIZE (sizeof(struct inotify_event))
189 
190 /* reasonable guess as to size of 1024 events */
191 #define BUF_LEN (1024 * (EVENT_SIZE + 16))
192 
193  char buf[BUF_LEN];
194  ssize_t i = 0;
195 
196  const auto len = read(m_fd, buf, BUF_LEN);
197  if (len < 0)
198  {
199  if (errno == EINTR)
200  {
201  /* need to reissue system call */
202  }
203  else
204  perror("[CFileSystemWatcher::getChanges] read");
205  }
206  else if (!len)
207  {
208  /* BUF_LEN too small? */
209  }
210 
211  while (i < len)
212  {
213  struct inotify_event event_val
214  {
215  };
216  std::memcpy(
217  &event_val, &buf[i],
218  sizeof(event_val)); // Was: event = (struct inotify_event *) ;
219  struct inotify_event* event = &event_val;
220 
221  i += EVENT_SIZE + event->len;
222 
223  // printf ("wd=%d mask=%u cookie=%u len=%u\n",event->wd,
224  // event->mask,event->cookie, event->len);
225 
226  string eventName;
227  if (event->len) eventName = event->name;
228 
229  // Add event to output list:
230  // ---------------------------------------------
231  if (0 == (event->mask & IN_UNMOUNT) &&
232  0 == (event->mask & IN_Q_OVERFLOW) &&
233  0 == (event->mask & IN_IGNORED))
234  {
235  TFileSystemChange newEntry;
236 
237  newEntry.path = m_watchedDirectory + eventName;
238  newEntry.isDir = event->mask & IN_ISDIR;
239  newEntry.eventModified = event->mask & IN_MODIFY;
240  newEntry.eventCloseWrite = event->mask & IN_CLOSE_WRITE;
241  newEntry.eventDeleted = event->mask & IN_DELETE;
242  newEntry.eventMovedTo = event->mask & IN_MOVED_TO;
243  newEntry.eventMovedFrom = event->mask & IN_MOVED_FROM;
244  newEntry.eventCreated = event->mask & IN_CREATE;
245  newEntry.eventAccessed = event->mask & IN_ACCESS;
246 
247  out_list.push_back(newEntry);
248  }
249  }
250  }
251 #endif
252 #endif
253 }
254 
255 #ifdef _WIN32
256 
258 {
259  uint8_t buf[8 * 1024];
260  DWORD dwRead = 0;
261 
262  while (ReadDirectoryChangesW(
263  HANDLE(m_hNotif), buf, sizeof(buf),
264  false, // No subtree
265  FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME |
266  FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE |
267  FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_LAST_ACCESS |
268  FILE_NOTIFY_CHANGE_CREATION,
269  &dwRead, nullptr, nullptr))
270  {
271  // Interpret read data as FILE_NOTIFY_INFORMATION:
272  // There might be several notifications in the same data block:
273  size_t idx = 0;
274  for (;;)
275  {
276  // Yep... this is MS's idea of a beautiful and easy way to return
277  // data:
278  FILE_NOTIFY_INFORMATION* fni =
279  reinterpret_cast<FILE_NOTIFY_INFORMATION*>(&buf[idx]);
280 
281  // Extract the name (stored as a WCHAR*) into a UTF-8 std::string:
282  ASSERTMSG_(
283  fni->FileNameLength < 10000,
284  "Name length >10K... this is probably an error");
285 
286  int reqLen = WideCharToMultiByte(
287  CP_UTF8, 0, fni->FileName, fni->FileNameLength >> 1, nullptr, 0,
288  nullptr, nullptr);
289  std::vector<char> tmpBuf(reqLen);
290  int actLen = WideCharToMultiByte(
291  CP_UTF8, 0, fni->FileName, fni->FileNameLength >> 1, &tmpBuf[0],
292  tmpBuf.size(), nullptr, nullptr);
293  ASSERTMSG_(
294  actLen > 0, "Error converting filename from WCHAR* to UTF8");
295 
296  const std::string filName(&tmpBuf[0], actLen);
297 
298  TFileSystemChange newEntry;
299  newEntry.path = m_watchedDirectory + filName;
300  newEntry.isDir = mrpt::system::directoryExists(newEntry.path);
301 
302  // Fill out the new entry:
303  std::lock_guard<std::mutex> lock(m_queue_events_win32_cs);
304  switch (fni->Action)
305  {
306  case FILE_ACTION_ADDED:
307  {
308  newEntry.eventCreated = true;
310  new TFileSystemChange(newEntry));
311  }
312  break;
313  case FILE_ACTION_REMOVED:
314  {
315  newEntry.eventDeleted = true;
317  new TFileSystemChange(newEntry));
318  }
319  break;
320  case FILE_ACTION_MODIFIED:
321  {
322  newEntry.eventModified = true;
324  new TFileSystemChange(newEntry));
325  }
326  break;
327  case FILE_ACTION_RENAMED_OLD_NAME:
328  {
329  newEntry.eventMovedFrom = true;
331  new TFileSystemChange(newEntry));
332  }
333  break;
334  case FILE_ACTION_RENAMED_NEW_NAME:
335  {
336  newEntry.eventMovedTo = true;
338  new TFileSystemChange(newEntry));
339  }
340  break;
341  }
342 
343  // Next entry?
344  if (fni->NextEntryOffset > 0)
345  idx += fni->NextEntryOffset;
346  else
347  break; // done
348  }
349  }
350 
351  printf("Done!");
352 }
353 #endif
#define MRPT_START
Definition: exceptions.h:241
#define THROW_EXCEPTION(msg)
Definition: exceptions.h:67
std::queue< TFileSystemChange * > m_queue_events_win32_msgs
std::string path
Complete path of the file/directory that has changed.
STL namespace.
void getChanges(TFileSystemChangeList &out_list)
Call this method sometimes to get the list of changes in the watched directory.
std::deque< TFileSystemChange > TFileSystemChangeList
std::string m_watchedDirectory
Ended in "/".
#define ASSERT_(f)
Defines an assertion mechanism.
Definition: exceptions.h:120
void thread_win32_watch()
Watch thread; only needed in win32.
#define ASSERTMSG_(f, __ERROR_MSG)
Defines an assertion mechanism.
Definition: exceptions.h:108
bool isDir
Whether the event happened to a file or a directory.
virtual ~CFileSystemWatcher()
Destructor.
#define MRPT_END
Definition: exceptions.h:245
Each of the changes detected by utils::CFileSystemWatcher.
bool directoryExists(const std::string &fileName)
Test if a given directory exists (it fails if the given path refers to an existing file)...
Definition: filesystem.cpp:137
void memcpy(void *dest, size_t destSize, const void *src, size_t copyCount) noexcept
An OS and compiler independent version of "memcpy".
CFileSystemWatcher(const std::string &path)
Creates the subscription to a specified path.



Page generated by Doxygen 1.8.14 for MRPT 1.9.9 Git: c7a3bec24 Sun Mar 29 18:33:13 2020 +0200 at dom mar 29 18:50:38 CEST 2020