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
39namespace fawkes {
40
41/* Supported events suitable for MASK parameter of INOTIFY_ADD_WATCH. */
42/** File was accessed. */
43const unsigned int FamListener::FAM_ACCESS = 0x00000001;
44/** File was modified. */
45const unsigned int FamListener::FAM_MODIFY = 0x00000002;
46/** Metadata changed. */
47const unsigned int FamListener::FAM_ATTRIB = 0x00000004;
48/** Writtable file was closed. */
49const unsigned int FamListener::FAM_CLOSE_WRITE = 0x00000008;
50/** Unwrittable file closed. */
51const unsigned int FamListener::FAM_CLOSE_NOWRITE = 0x00000010;
52/** Close. */
53const unsigned int FamListener::FAM_CLOSE = (FAM_CLOSE_WRITE | FAM_CLOSE_NOWRITE);
54/** File was opened. */
55const unsigned int FamListener::FAM_OPEN = 0x00000020;
56/** File was moved from X. */
57const unsigned int FamListener::FAM_MOVED_FROM = 0x00000040;
58/** File was moved to Y. */
59const unsigned int FamListener::FAM_MOVED_TO = 0x00000080;
60/** Moves. */
61const unsigned int FamListener::FAM_MOVE = (FAM_MOVED_FROM | FAM_MOVED_TO);
62/** Subfile was created. */
63const unsigned int FamListener::FAM_CREATE = 0x00000100;
64/** Subfile was deleted. */
65const unsigned int FamListener::FAM_DELETE = 0x00000200;
66/** Self was deleted. */
67const unsigned int FamListener::FAM_DELETE_SELF = 0x00000400;
68/** Self was moved. */
69const unsigned int FamListener::FAM_MOVE_SELF = 0x00000800;
70
71/* Events sent by the kernel. */
72/** Backing fs was unmounted. */
73const unsigned int FamListener::FAM_UNMOUNT = 0x00002000;
74/** Event queued overflowed. */
75const unsigned int FamListener::FAM_Q_OVERFLOW = 0x00004000;
76/** File was ignored. */
77const unsigned int FamListener::FAM_IGNORED = 0x00008000;
78
79/* Special flags. */
80/** Only watch the path if it is a directory. */
81const unsigned int FamListener::FAM_ONLYDIR = 0x01000000;
82/** Do not follow a sym link. */
83const unsigned int FamListener::FAM_DONT_FOLLOW = 0x02000000;
84/** Add to the mask of an already existing watch. */
85const unsigned int FamListener::FAM_MASK_ADD = 0x20000000;
86/** Event occurred against dir. */
87const unsigned int FamListener::FAM_ISDIR = 0x40000000;
88/** Only send event once. */
89const unsigned int FamListener::FAM_ONESHOT = 0x80000000;
90
91/** All events which a program can wait on. */
92const 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 */
155void
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 */
204void
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. */
221void
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 */
245void
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 */
262void
264{
265 listeners_.push_back_locked(listener);
266}
267
268/** Remove a listener.
269 * @param listener listener to remove
270 */
271void
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 */
282void
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 */
412void
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.