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