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