Fawkes API  Fawkes Development Version
fam.cpp
1 
2 /***************************************************************************
3  * fam.h - File Alteration Monitor
4  *
5  * Created: Fri May 23 11:38:41 2008
6  * Copyright 2006-2008 Tim Niemueller [www.niemueller.de]
7  *
8  ****************************************************************************/
9 
10 /* This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU Library General Public License for more details.
19  *
20  * Read the full text in the LICENSE.GPL file in the doc directory.
21  */
22 
23 #include <utils/system/fam.h>
24 #include <core/exception.h>
25 #include <logging/liblogger.h>
26 
27 #ifdef HAVE_INOTIFY
28 # include <sys/inotify.h>
29 # include <sys/stat.h>
30 # include <poll.h>
31 # include <dirent.h>
32 # include <cstring>
33 #endif
34 #include <cerrno>
35 #include <cstdlib>
36 #include <unistd.h>
37 
38 namespace fawkes {
39 
40 /* Supported events suitable for MASK parameter of INOTIFY_ADD_WATCH. */
41 /** File was accessed. */
42 const unsigned int FamListener::FAM_ACCESS = 0x00000001;
43 /** File was modified. */
44 const unsigned int FamListener::FAM_MODIFY = 0x00000002;
45 /** Metadata changed. */
46 const unsigned int FamListener::FAM_ATTRIB = 0x00000004;
47 /** Writtable file was closed. */
48 const unsigned int FamListener::FAM_CLOSE_WRITE = 0x00000008;
49 /** Unwrittable file closed. */
50 const unsigned int FamListener::FAM_CLOSE_NOWRITE = 0x00000010;
51 /** Close. */
52 const unsigned int FamListener::FAM_CLOSE = (FAM_CLOSE_WRITE | FAM_CLOSE_NOWRITE);
53 /** File was opened. */
54 const unsigned int FamListener::FAM_OPEN = 0x00000020;
55 /** File was moved from X. */
56 const unsigned int FamListener::FAM_MOVED_FROM = 0x00000040;
57 /** File was moved to Y. */
58 const unsigned int FamListener::FAM_MOVED_TO = 0x00000080;
59 /** Moves. */
60 const unsigned int FamListener::FAM_MOVE = (FAM_MOVED_FROM | FAM_MOVED_TO);
61 /** Subfile was created. */
62 const unsigned int FamListener::FAM_CREATE = 0x00000100;
63 /** Subfile was deleted. */
64 const unsigned int FamListener::FAM_DELETE = 0x00000200;
65 /** Self was deleted. */
66 const unsigned int FamListener::FAM_DELETE_SELF = 0x00000400;
67 /** Self was moved. */
68 const unsigned int FamListener::FAM_MOVE_SELF = 0x00000800;
69 
70 /* Events sent by the kernel. */
71 /** Backing fs was unmounted. */
72 const unsigned int FamListener::FAM_UNMOUNT = 0x00002000;
73 /** Event queued overflowed. */
74 const unsigned int FamListener::FAM_Q_OVERFLOW = 0x00004000;
75 /** File was ignored. */
76 const unsigned int FamListener::FAM_IGNORED = 0x00008000;
77 
78 /* Special flags. */
79 /** Only watch the path if it is a directory. */
80 const unsigned int FamListener::FAM_ONLYDIR = 0x01000000;
81 /** Do not follow a sym link. */
82 const unsigned int FamListener::FAM_DONT_FOLLOW = 0x02000000;
83 /** Add to the mask of an already existing watch. */
84 const unsigned int FamListener::FAM_MASK_ADD = 0x20000000;
85 /** Event occurred against dir. */
86 const unsigned int FamListener::FAM_ISDIR = 0x40000000;
87 /** Only send event once. */
88 const unsigned int FamListener::FAM_ONESHOT = 0x80000000;
89 
90 /** All events which a program can wait on. */
91 const unsigned int FamListener::FAM_ALL_EVENTS = (FAM_ACCESS | FAM_MODIFY | FAM_ATTRIB | FAM_CLOSE_WRITE \
92  | FAM_CLOSE_NOWRITE | FAM_OPEN | FAM_MOVED_FROM \
93  | FAM_MOVED_TO | FAM_CREATE | FAM_DELETE \
94  | FAM_DELETE_SELF | FAM_MOVE_SELF);
95 
96 
97 /** @class FileAlterationMonitor <utils/system/fam.h>
98  * Monitors files for changes.
99  * This is a wrapper around inotify. It will watch directories and files
100  * for modifications. If a modifiacation, removal or addition of a file
101  * is detected one or more listeners are called. The files which trigger
102  * the event can be constrained with regular expressions.
103  * @author Tim Niemueller
104  */
105 
106 /** Constructor.
107  * Opens the inotify context.
108  */
110 {
111  __inotify_fd = -1;
112  __inotify_buf = NULL;
113  __inotify_bufsize = 0;
114 
115 #ifdef HAVE_INOTIFY
116  if ( (__inotify_fd = inotify_init()) == -1 ) {
117  throw Exception(errno, "Failed to initialize inotify");
118  }
119 
120  // from http://www.linuxjournal.com/article/8478
121  __inotify_bufsize = 1024 * (sizeof(struct inotify_event) + 16);
122  __inotify_buf = (char *)malloc(__inotify_bufsize);
123 #endif
124 
125  __interrupted = false;
126  __interruptible = (pipe(__pipe_fds) == 0);
127 
128  __regexes.clear();
129 }
130 
131 
132 /** Destructor. */
134 {
135  for (__rxit = __regexes.begin(); __rxit != __regexes.end(); ++__rxit) {
136  regfree(*__rxit);
137  free(*__rxit);
138  }
139 
140 #ifdef HAVE_INOTIFY
141  for (__inotify_wit = __inotify_watches.begin(); __inotify_wit != __inotify_watches.end(); ++__inotify_wit) {
142  inotify_rm_watch(__inotify_fd, __inotify_wit->first);
143  }
144  close(__inotify_fd);
145  if ( __inotify_buf ) {
146  free(__inotify_buf);
147  __inotify_buf = NULL;
148  }
149 #endif
150 }
151 
152 
153 /** Watch a directory.
154  * This adds the given directory recursively to this FAM.
155  * @param dirpath path to directory to add
156  */
157 void
159 {
160 #ifdef HAVE_INOTIFY
161  DIR *d = opendir(dirpath);
162  if ( d == NULL ) {
163  throw Exception(errno, "Failed to open dir %s", dirpath);
164  }
165 
166  uint32_t mask = IN_MODIFY | IN_MOVE | IN_CREATE | IN_DELETE | IN_DELETE_SELF;
167  int iw;
168 
169  //LibLogger::log_debug("FileAlterationMonitor", "Adding watch for %s", dirpath);
170  if ( (iw = inotify_add_watch(__inotify_fd, dirpath, mask)) >= 0) {
171  __inotify_watches[iw] = dirpath;
172 
173  dirent *de;
174  while ( (de = readdir(d)) ) {
175  std::string fp = std::string(dirpath) + "/" + de->d_name;
176  struct stat st;
177  if ( stat(fp.c_str(), &st) == 0 ) {
178  if ( (de->d_name[0] != '.') && S_ISDIR(st.st_mode) ) {
179  try {
180  watch_dir(fp.c_str());
181  } catch (Exception &e) {
182  closedir(d);
183  throw;
184  }
185  //} else {
186  //LibLogger::log_debug("SkillerExecutionThread", "Skipping file %s", fp.c_str());
187  }
188  } else {
189  //LibLogger::log_debug("FileAlterationMonitor",
190  // "Skipping watch on %s, cannot stat (%s)",
191  // fp.c_str(), strerror(errno));
192  }
193  }
194  } else {
195  throw Exception(errno, "FileAlterationMonitor: cannot add watch for %s", dirpath);
196  }
197 
198  closedir(d);
199 #endif
200 }
201 
202 /** Watch a file.
203  * This adds the given fileto this FAM.
204  * @param filepath path to file to add
205  */
206 void
208 {
209 #ifdef HAVE_INOTIFY
210  uint32_t mask = IN_MODIFY | IN_MOVE | IN_CREATE | IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF;
211  int iw;
212 
213  if ( (iw = inotify_add_watch(__inotify_fd, filepath, mask)) >= 0) {
214  //LibLogger::log_debug("FileAlterationMonitor", "Added watch for %s: %i", filepath, iw);
215  __inotify_watches[iw] = filepath;
216  } else {
217  throw Exception("FileAlterationMonitor: cannot add watch for file %s", filepath);
218  }
219 #endif
220 }
221 
222 
223 /** Remove all currently active watches. */
224 void
226 {
227 #ifdef HAVE_INOTIFY
228  std::map<int, std::string>::iterator wit;
229  for (wit = __inotify_watches.begin(); wit != __inotify_watches.end(); ++wit) {
230  inotify_rm_watch(__inotify_fd, wit->first);
231  }
232  __inotify_watches.clear();
233 #endif
234 }
235 
236 /** Add a filter.
237  * Filters are applied to path names that triggered an event. All
238  * pathnames are checked against this regex and if any does not match
239  * the event is not posted to listeners.
240  * An example regular expression is
241  * @code
242  * ^[^.].*\\.lua$
243  * @endcode
244  * This regular expression matches to all files that does not start with
245  * a dot and have an .lua ending.
246  * @param regex regular expression to add
247  */
248 void
250 {
251  int regerr = 0;
252  regex_t *rx = (regex_t *)malloc(sizeof(regex_t));
253  if ( (regerr = regcomp(rx, regex, REG_EXTENDED)) != 0 ) {
254  char errtmp[1024];
255  regerror(regerr, rx, errtmp, sizeof(errtmp));
256  free(rx);
257  throw Exception("Failed to compile lua file regex: %s", errtmp);
258  }
259  __regexes.push_back_locked(rx);
260 }
261 
262 
263 /** Add a listener.
264  * @param listener listener to add
265  */
266 void
268 {
269  __listeners.push_back_locked(listener);
270 }
271 
272 
273 /** Remove a listener.
274  * @param listener listener to remove
275  */
276 void
278 {
279  __listeners.remove_locked(listener);
280 }
281 
282 
283 /** Process events.
284  * Call this when you want file events to be processed.
285  * @param timeout timeout in milliseconds to wait for an event, 0 to just check
286  * and no wait, -1 to wait forever until an event is received
287  */
288 void
290 {
291 #ifdef HAVE_INOTIFY
292  // Check for inotify events
293  __interrupted = false;
294  std::map<std::string, unsigned int> events;
295 
296  pollfd ipfd[2];
297  ipfd[0].fd = __inotify_fd;
298  ipfd[0].events = POLLIN;
299  ipfd[0].revents = 0;
300  ipfd[1].fd = __pipe_fds[0];
301  ipfd[1].events = POLLIN;
302  ipfd[1].revents = 0;
303  int prv = poll(ipfd, 2, timeout);
304  if ( prv == -1 ) {
305  if ( errno != EINTR ) {
306  //LibLogger::log_error("FileAlterationMonitor",
307  // "inotify poll failed: %s (%i)",
308  // strerror(errno), errno);
309  } else {
310  __interrupted = true;
311  }
312  } else while ( !__interrupted && (prv > 0) ) {
313  // Our fd has an event, we can read
314  if ( ipfd[0].revents & POLLERR ) {
315  //LibLogger::log_error("FileAlterationMonitor", "inotify poll error");
316  } else if (__interrupted) {
317  // interrupted
318  return;
319  } else {
320  // must be POLLIN
321  int bytes = 0, i = 0;
322 
323  if ((bytes = read(__inotify_fd, __inotify_buf, __inotify_bufsize)) != -1) {
324  while (!__interrupted && (i < bytes)) {
325  struct inotify_event *event = (struct inotify_event *) &__inotify_buf[i];
326 
327  if (event->mask & IN_IGNORED) {
328  i += sizeof(struct inotify_event) + event->len;
329  continue;
330  }
331 
332  bool valid = true;
333  if (! (event->mask & IN_ISDIR)) {
334  for (__rxit = __regexes.begin(); __rxit != __regexes.end(); ++__rxit) {
335  if (event->len > 0 &&
336  (regexec(*__rxit, event->name, 0, NULL, 0) == REG_NOMATCH))
337  {
338  valid = false;
339  break;
340  }
341  }
342  }
343 
344  if ( valid ) {
345  if (event->len == 0) {
346  if (__inotify_watches.find(event->wd) != __inotify_watches.end()) {
347  if (events.find(__inotify_watches[event->wd]) != events.end()) {
348  events[__inotify_watches[event->wd]] |= event->mask;
349  } else {
350  events[__inotify_watches[event->wd]] = event->mask;
351  }
352  }
353  } else {
354  if (events.find(event->name) != events.end()) {
355  events[event->name] |= event->mask;
356  } else {
357  events[event->name] = event->mask;
358  }
359  }
360  }
361 
362  if (event->mask & IN_DELETE_SELF) {
363  //printf("Watched %s has been deleted", event->name);
364  __inotify_watches.erase(event->wd);
365  inotify_rm_watch(__inotify_fd, event->wd);
366  }
367 
368  if (event->mask & IN_CREATE) {
369  // Check if it is a directory, if it is, watch it
370  std::string fp = __inotify_watches[event->wd] + "/" + event->name;
371  if ( (event->mask & IN_ISDIR) && (event->name[0] != '.') ) {
372  /*
373  LibLogger::log_debug("FileAlterationMonitor",
374  "Directory %s has been created, "
375  "adding to watch list", event->name);
376  */
377  try {
378  watch_dir(fp.c_str());
379  } catch (Exception &e) {
380  //LibLogger::log_warn("FileAlterationMonitor", "Adding watch for %s failed, ignoring.", fp.c_str());
381  //LibLogger::log_warn("FileAlterationMonitor", e);
382  }
383  }
384  }
385 
386  i += sizeof(struct inotify_event) + event->len;
387  }
388 
389  }
390  }
391 
392  // Give some time to wait for related events to pipe in, we still
393  // do not guarantee to merge them all, but we do a little better
394  usleep(1000);
395  prv = poll(ipfd, 2, 0);
396  }
397 
398  std::map<std::string, unsigned int>::const_iterator e;
399  for (e = events.begin(); e != events.end(); ++e) {
400  //LibLogger::log_warn("FileAlterationMonitor", "Event %s %x",
401  // e->first.c_str(), e->second);
402  for (__lit = __listeners.begin(); __lit != __listeners.end(); ++__lit) {
403  (*__lit)->fam_event(e->first.c_str(), e->second);
404  }
405  }
406 
407 #else
408  //LibLogger::log_error("FileAlterationMonitor",
409 // "inotify support not available, but "
410 // "process_events() was called. Ignoring.");
411  throw Exception("FileAlterationMonitor: inotify support not available, "
412  "but process_events() was called.");
413 #endif
414 }
415 
416 
417 /** Interrupt a running process_events().
418  * This method will interrupt e.g. a running inifinetly blocking call of
419  * process_events().
420  */
421 void
423 {
424  if (__interruptible) {
425  __interrupted = true;
426  char tmp = 0;
427  if (write(__pipe_fds[1], &tmp, 1) != 1) {
428  throw Exception(errno, "Failed to interrupt file alteration monitor,"
429  " failed to write to pipe");
430  }
431  } else {
432  throw Exception("Currently not interruptible");
433  }
434 }
435 
436 
437 /** @class FamListener <utils/system/fam.h>
438  * File Alteration Monitor Listener.
439  * Listener called by FileAlterationMonitor for events.
440  * @author Tim Niemueller
441  *
442  * @fn FamListener::fam_event(const char *filename, unsigned int mask)
443  * Event has been raised.
444  * @param filename name of the file that triggered the event
445  * @param mask mask indicating the event. Currently inotify event flags
446  * are used, see inotify.h.
447  *
448  */
449 
450 /** Virtual empty destructor. */
452 {
453 }
454 
455 } // end of namespace fawkes
Fawkes library namespace.
void reset()
Remove all currently active watches.
Definition: fam.cpp:225
static const unsigned int FAM_UNMOUNT
Backing fs was unmounted.
Definition: fam.h:55
static const unsigned int FAM_ONESHOT
Only send event once.
Definition: fam.h:63
void add_filter(const char *regex)
Add a filter.
Definition: fam.cpp:249
static const unsigned int FAM_CREATE
Subfile was created.
Definition: fam.h:50
void interrupt()
Interrupt a running process_events().
Definition: fam.cpp:422
void push_back_locked(const Type &x)
Push element to list at back with lock protection.
Definition: lock_list.h:152
static const unsigned int FAM_ISDIR
Event occurred against dir.
Definition: fam.h:62
static const unsigned int FAM_MOVE
Moves.
Definition: fam.h:49
static const unsigned int FAM_OPEN
File was opened.
Definition: fam.h:46
static const unsigned int FAM_ACCESS
File was accessed.
Definition: fam.h:40
~FileAlterationMonitor()
Destructor.
Definition: fam.cpp:133
static const unsigned int FAM_IGNORED
File was ignored.
Definition: fam.h:57
Base class for exceptions in Fawkes.
Definition: exception.h:36
static const unsigned int FAM_Q_OVERFLOW
Event queued overflowed.
Definition: fam.h:56
static const unsigned int FAM_ONLYDIR
Only watch the path if it is a directory.
Definition: fam.h:59
static const unsigned int FAM_MASK_ADD
Add to the mask of an already existing watch.
Definition: fam.h:61
File Alteration Monitor Listener.
Definition: fam.h:35
static const unsigned int FAM_MOVE_SELF
Self was moved.
Definition: fam.h:53
static const unsigned int FAM_MOVED_FROM
File was moved from X.
Definition: fam.h:47
FileAlterationMonitor()
Constructor.
Definition: fam.cpp:109
static const unsigned int FAM_MOVED_TO
File was moved to Y.
Definition: fam.h:48
static const unsigned int FAM_DELETE_SELF
Self was deleted.
Definition: fam.h:52
void remove_listener(FamListener *listener)
Remove a listener.
Definition: fam.cpp:277
static const unsigned int FAM_ALL_EVENTS
All events which a program can wait on.
Definition: fam.h:65
void add_listener(FamListener *listener)
Add a listener.
Definition: fam.cpp:267
static const unsigned int FAM_CLOSE_NOWRITE
Unwrittable file closed.
Definition: fam.h:44
static const unsigned int FAM_DELETE
Subfile was deleted.
Definition: fam.h:51
static const unsigned int FAM_MODIFY
File was modified.
Definition: fam.h:41
void watch_dir(const char *dirpath)
Watch a directory.
Definition: fam.cpp:158
void process_events(int timeout=0)
Process events.
Definition: fam.cpp:289
static const unsigned int FAM_DONT_FOLLOW
Do not follow a sym link.
Definition: fam.h:60
void watch_file(const char *filepath)
Watch a file.
Definition: fam.cpp:207
static const unsigned int FAM_ATTRIB
Metadata changed.
Definition: fam.h:42
virtual ~FamListener()
Virtual empty destructor.
Definition: fam.cpp:451
static const unsigned int FAM_CLOSE_WRITE
Writtable file was closed.
Definition: fam.h:43
static const unsigned int FAM_CLOSE
Close.
Definition: fam.h:45