Fawkes API Fawkes Development Version

fam.cpp

00001 
00002 /***************************************************************************
00003  *  fam.h - File Alteration Monitor
00004  *
00005  *  Created: Fri May 23 11:38:41 2008
00006  *  Copyright  2006-2008  Tim Niemueller [www.niemueller.de]
00007  *
00008  ****************************************************************************/
00009 
00010 /*  This program is free software; you can redistribute it and/or modify
00011  *  it under the terms of the GNU General Public License as published by
00012  *  the Free Software Foundation; either version 2 of the License, or
00013  *  (at your option) any later version.
00014  *
00015  *  This program is distributed in the hope that it will be useful,
00016  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00017  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00018  *  GNU Library General Public License for more details.
00019  *
00020  *  Read the full text in the LICENSE.GPL file in the doc directory.
00021  */
00022 
00023 #include <utils/system/fam.h>
00024 #include <utils/logging/liblogger.h>
00025 
00026 #ifdef HAVE_INOTIFY
00027 #  include <sys/inotify.h>
00028 #  include <sys/stat.h>
00029 #  include <poll.h>
00030 #  include <dirent.h>
00031 #  include <unistd.h>
00032 #  include <cstring>
00033 #endif
00034 #include <cerrno>
00035 #include <cstdlib>
00036 
00037 namespace fawkes {
00038 
00039 /* Supported events suitable for MASK parameter of INOTIFY_ADD_WATCH.  */
00040 /** File was accessed.  */
00041 const unsigned int FamListener::FAM_ACCESS        = 0x00000001;
00042 /** File was modified.  */
00043 const unsigned int FamListener::FAM_MODIFY        = 0x00000002;
00044 /** Metadata changed.  */
00045 const unsigned int FamListener::FAM_ATTRIB        = 0x00000004;
00046 /** Writtable file was closed.  */
00047 const unsigned int FamListener::FAM_CLOSE_WRITE   = 0x00000008;
00048 /** Unwrittable file closed.  */
00049 const unsigned int FamListener::FAM_CLOSE_NOWRITE = 0x00000010;
00050 /** Close.  */
00051 const unsigned int FamListener::FAM_CLOSE         = (FAM_CLOSE_WRITE | FAM_CLOSE_NOWRITE);
00052 /** File was opened.  */
00053 const unsigned int FamListener::FAM_OPEN          = 0x00000020;
00054 /** File was moved from X.  */
00055 const unsigned int FamListener::FAM_MOVED_FROM    = 0x00000040;
00056 /** File was moved to Y.  */
00057 const unsigned int FamListener::FAM_MOVED_TO      = 0x00000080;
00058 /** Moves.  */
00059 const unsigned int FamListener::FAM_MOVE          = (FAM_MOVED_FROM | FAM_MOVED_TO);
00060 /** Subfile was created.  */
00061 const unsigned int FamListener::FAM_CREATE        = 0x00000100;
00062 /** Subfile was deleted.  */
00063 const unsigned int FamListener::FAM_DELETE        = 0x00000200;
00064 /** Self was deleted.  */
00065 const unsigned int FamListener::FAM_DELETE_SELF   = 0x00000400;
00066 /** Self was moved.  */
00067 const unsigned int FamListener::FAM_MOVE_SELF     = 0x00000800;
00068 
00069 /* Events sent by the kernel.  */
00070 /** Backing fs was unmounted.  */
00071 const unsigned int FamListener::FAM_UNMOUNT       = 0x00002000;
00072 /** Event queued overflowed.  */
00073 const unsigned int FamListener::FAM_Q_OVERFLOW    = 0x00004000;
00074 /** File was ignored.  */
00075 const unsigned int FamListener::FAM_IGNORED       = 0x00008000;
00076 
00077 /* Special flags.  */
00078 /** Only watch the path if it is a directory.  */
00079 const unsigned int FamListener::FAM_ONLYDIR       = 0x01000000;
00080 /** Do not follow a sym link.  */
00081 const unsigned int FamListener::FAM_DONT_FOLLOW   = 0x02000000;
00082 /** Add to the mask of an already existing watch.  */
00083 const unsigned int FamListener::FAM_MASK_ADD      = 0x20000000;
00084 /** Event occurred against dir.  */
00085 const unsigned int FamListener::FAM_ISDIR         = 0x40000000;
00086 /** Only send event once.  */
00087 const unsigned int FamListener::FAM_ONESHOT       = 0x80000000;
00088 
00089 /** All events which a program can wait on.  */
00090 const unsigned int FamListener::FAM_ALL_EVENTS     = (FAM_ACCESS | FAM_MODIFY | FAM_ATTRIB | FAM_CLOSE_WRITE \
00091                                                       | FAM_CLOSE_NOWRITE | FAM_OPEN | FAM_MOVED_FROM \
00092                                                       | FAM_MOVED_TO | FAM_CREATE | FAM_DELETE \
00093                                                       | FAM_DELETE_SELF | FAM_MOVE_SELF);
00094 
00095 
00096 /** @class FileAlterationMonitor <utils/system/fam.h>
00097  * Monitors files for changes.
00098  * This is a wrapper around inotify. It will watch directories and files
00099  * for modifications. If a modifiacation, removal or addition of a file
00100  * is detected one or more listeners are called. The files which trigger
00101  * the event can be constrained with regular expressions.
00102  * @author Tim Niemueller
00103  */
00104 
00105 /** Constructor.
00106  * Opens the inotify context.
00107  */
00108 FileAlterationMonitor::FileAlterationMonitor()
00109 {
00110 #ifdef HAVE_INOTIFY
00111   if ( (__inotify_fd = inotify_init()) == -1 ) {
00112     throw Exception(errno, "Failed to initialize inotify");
00113   }
00114 
00115   // from http://www.linuxjournal.com/article/8478
00116   __inotify_bufsize = 1024 * (sizeof(struct inotify_event) + 16);
00117   __inotify_buf     = (char *)malloc(__inotify_bufsize);
00118 #endif
00119 
00120   __interrupted   = false;
00121   __interruptible = (pipe(__pipe_fds) == 0);
00122 
00123   __regexes.clear();
00124 }
00125 
00126 
00127 /** Destructor. */
00128 FileAlterationMonitor::~FileAlterationMonitor()
00129 {
00130   for (__rxit = __regexes.begin(); __rxit != __regexes.end(); ++__rxit) {
00131     regfree(*__rxit);
00132     free(*__rxit);
00133   }
00134 
00135 #ifdef HAVE_INOTIFY
00136   for (__inotify_wit = __inotify_watches.begin(); __inotify_wit != __inotify_watches.end(); ++__inotify_wit) {
00137     inotify_rm_watch(__inotify_fd, __inotify_wit->first);
00138   }
00139   close(__inotify_fd);
00140   if ( __inotify_buf ) {
00141     free(__inotify_buf);
00142     __inotify_buf = NULL;
00143   }
00144 #endif
00145 }
00146 
00147 
00148 /** Watch a directory.
00149  * This adds the given directory recursively to this FAM.
00150  * @param dirpath path to directory to add
00151  */
00152 void
00153 FileAlterationMonitor::watch_dir(const char *dirpath)
00154 {
00155 #ifdef HAVE_INOTIFY
00156   DIR *d = opendir(dirpath);
00157   if ( d == NULL ) {
00158     throw Exception(errno, "Failed to open dir %s", dirpath);
00159   }
00160 
00161   uint32_t mask = IN_MODIFY | IN_MOVE | IN_CREATE | IN_DELETE | IN_DELETE_SELF;
00162   int iw;
00163 
00164   //LibLogger::log_debug("FileAlterationMonitor", "Adding watch for %s", dirpath);
00165   if ( (iw = inotify_add_watch(__inotify_fd, dirpath, mask)) >= 0) {
00166     __inotify_watches[iw] = dirpath;
00167 
00168     dirent de, *res;
00169     while ( (readdir_r(d, &de, &res) == 0) && (res != NULL) ) {
00170       std::string fp = std::string(dirpath) + "/" + de.d_name;
00171       struct stat st;
00172       if ( stat(fp.c_str(), &st) == 0 ) {
00173         if ( (de.d_name[0] != '.') && S_ISDIR(st.st_mode) ) {
00174           try {
00175             watch_dir(fp.c_str());
00176           } catch (Exception &e) {
00177             closedir(d);
00178             throw;
00179           }
00180         //} else {
00181           //LibLogger::log_debug("SkillerExecutionThread", "Skipping file %s", fp.c_str());       
00182         }
00183       } else {
00184         LibLogger::log_debug("FileAlterationMonitor",
00185                              "Skipping watch on %s, cannot stat (%s)",
00186                              fp.c_str(), strerror(errno));
00187       }
00188     }
00189   } else {
00190     throw Exception("FileAlterationMonitor",
00191                     "Cannot add watch for %s", dirpath);
00192   }
00193 
00194   closedir(d);
00195 #endif
00196 }
00197 
00198 /** Watch a file.
00199  * This adds the given fileto this FAM.
00200  * @param filepath path to file to add
00201  */
00202 void
00203 FileAlterationMonitor::watch_file(const char *filepath)
00204 {
00205 #ifdef HAVE_INOTIFY
00206   uint32_t mask = IN_MODIFY | IN_MOVE | IN_CREATE | IN_DELETE | IN_DELETE_SELF;
00207   int iw;
00208 
00209   //LibLogger::log_debug("FileAlterationMonitor", "Adding watch for %s", dirpath);
00210   if ( (iw = inotify_add_watch(__inotify_fd, filepath, mask)) >= 0) {
00211     __inotify_watches[iw] = filepath;
00212   } else {
00213     throw Exception("FileAlterationMonitor",
00214                     "Cannot add watch for file %s", filepath);
00215   }
00216 #endif
00217 }
00218 
00219 
00220 /** Add a filter.
00221  * Filters are applied to path names that triggered an event. All
00222  * pathnames are checked against this regex and if any does not match
00223  * the event is not posted to listeners.
00224  * An example regular expression is
00225  * @code
00226  * ^[^.].*\\.lua$
00227  * @endcode
00228  * This regular expression matches to all files that does not start with
00229  * a dot and have an .lua ending.
00230  * @param regex regular expression to add
00231  */
00232 void
00233 FileAlterationMonitor::add_filter(const char *regex)
00234 {
00235   int regerr = 0;
00236   regex_t *rx = (regex_t *)malloc(sizeof(regex_t));
00237   if ( (regerr = regcomp(rx, regex, REG_EXTENDED)) != 0 ) {
00238     char errtmp[1024];
00239     regerror(regerr, rx, errtmp, sizeof(errtmp));
00240     free(rx);
00241     throw Exception("Failed to compile lua file regex: %s", errtmp);
00242   }
00243   __regexes.push_back_locked(rx);
00244 }
00245 
00246 
00247 /** Add a listener.
00248  * @param listener listener to add
00249  */
00250 void
00251 FileAlterationMonitor::add_listener(FamListener *listener)
00252 {
00253   __listeners.push_back_locked(listener);
00254 }
00255 
00256 
00257 /** Remove a listener.
00258  * @param listener listener to remove
00259  */
00260 void
00261 FileAlterationMonitor::remove_listener(FamListener *listener)
00262 {
00263   __listeners.remove_locked(listener);
00264 }
00265 
00266 
00267 /** Process events.
00268  * Call this when you want file events to be processed.
00269  * @param timeout timeout in milliseconds to wait for an event, 0 to just check
00270  * and no wait, -1 to wait forever until an event is received
00271  */
00272 void
00273 FileAlterationMonitor::process_events(int timeout)
00274 {
00275 #ifdef HAVE_INOTIFY
00276   // Check for inotify events
00277   __interrupted = false;
00278   pollfd ipfd[2];
00279   ipfd[0].fd = __inotify_fd;
00280   ipfd[0].events = POLLIN;
00281   ipfd[0].revents = 0;
00282   ipfd[1].fd = __pipe_fds[0];
00283   ipfd[1].events = POLLIN;
00284   ipfd[1].revents = 0;
00285   int prv = poll(ipfd, 2, timeout);
00286   if ( prv == -1 ) {
00287     if ( errno != EINTR ) {
00288       LibLogger::log_error("FileAlterationMonitor",
00289                            "inotify poll failed: %s (%i)",
00290                            strerror(errno), errno);
00291     } else {
00292       __interrupted = true;
00293     }
00294   } else while ( !__interrupted && (prv > 0) ) {
00295     // Our fd has an event, we can read
00296     if ( ipfd[0].revents & POLLERR ) {      
00297       LibLogger::log_error("FileAlterationMonitor", "inotify poll error");
00298     } else if (__interrupted) {
00299       // interrupted
00300       return;
00301     } else {
00302       // must be POLLIN
00303       int bytes = 0, i = 0;
00304       if ((bytes = read(__inotify_fd, __inotify_buf, __inotify_bufsize)) != -1) {
00305         while (!__interrupted && (i < bytes)) {
00306           struct inotify_event *event = (struct inotify_event *) &__inotify_buf[i];
00307           
00308           bool valid = true;
00309           if (! (event->mask & IN_ISDIR)) {
00310             for (__rxit = __regexes.begin(); __rxit != __regexes.end(); ++__rxit) {
00311               if (regexec(*__rxit, event->name, 0, NULL, 0) == REG_NOMATCH ) {
00312                 //LibLogger::log_debug("FileAlterationMonitor", "A regex did not match for %s", event->name);
00313                 valid = false;
00314                 break;
00315               }
00316             }
00317           }
00318 
00319           /*
00320           if (event->mask & IN_MODIFY) {
00321             LibLogger::log_debug("FileAlterationMonitor", "%s has been modified", event->name);
00322             }
00323           if (event->mask & IN_MOVE) {
00324             LibLogger::log_debug("FileAlterationMonitor", "%s has been moved", event->name);
00325             }
00326           if (event->mask & IN_DELETE) {
00327             LibLogger::log_debug("FileAlterationMonitor", "%s has been deleted", event->name);
00328           }
00329           if (event->mask & IN_CREATE) {
00330             LibLogger::log_debug("FileAlterationMonitor", "%s has been created", event->name);
00331           }
00332           */
00333 
00334           if ( valid ) {
00335             for (__lit = __listeners.begin(); __lit != __listeners.end(); ++__lit) {
00336               (*__lit)->fam_event(event->name, event->mask);
00337             }
00338           }
00339 
00340           if (event->mask & IN_DELETE_SELF) {
00341             //LibLogger::log_debug("FileAlterationMonitor", "Watched %s has been deleted", event->name);
00342             __inotify_watches.erase(event->wd);
00343             inotify_rm_watch(__inotify_fd, event->wd);
00344           }
00345 
00346           if (event->mask & IN_CREATE) {
00347             // Check if it is a directory, if it is, watch it
00348             std::string fp = __inotify_watches[event->wd] + "/" + event->name;
00349             if (  (event->mask & IN_ISDIR) && (event->name[0] != '.') ) {
00350               /*
00351               LibLogger::log_debug("FileAlterationMonitor",
00352                                    "Directory %s has been created, "
00353                                    "adding to watch list", event->name);
00354               */
00355               try {
00356                 watch_dir(fp.c_str());
00357               } catch (Exception &e) {
00358                 LibLogger::log_warn("FileAlterationMonitor", "Adding watch for %s failed, ignoring.", fp.c_str());
00359                 LibLogger::log_warn("FileAlterationMonitor", e);
00360               }
00361             }
00362           }
00363 
00364           i += sizeof(struct inotify_event) + event->len;
00365         }
00366       } else {
00367         LibLogger::log_error("FileAlterationMonitor", "inotify failed to read any bytes");
00368       }
00369     }
00370 
00371     prv = poll(ipfd, 2, 0);
00372   }
00373 #else
00374   LibLogger::log_error("FileAlterationMonitor",
00375                        "inotify support not available, but "
00376                        "process_events() was called. Ignoring.");
00377 #endif
00378 }
00379 
00380 
00381 /** Interrupt a running process_events().
00382  * This method will interrupt e.g. a running inifinetly blocking call of
00383  * process_events().
00384  */
00385 void
00386 FileAlterationMonitor::interrupt()
00387 {
00388   if (__interruptible) {
00389     __interrupted = true;
00390     char tmp = 0;
00391     if (write(__pipe_fds[1], &tmp, 1) != 1) {
00392       throw Exception(errno, "Failed to interrupt file alteration monitor,"
00393                              " failed to write to pipe");
00394     }
00395   } else {
00396     throw Exception("Currently not interruptible");
00397   }
00398 }
00399 
00400 
00401 /** @class FamListener <utils/system/fam.h>
00402  * File Alteration Monitor Listener.
00403  * Listener called by FileAlterationMonitor for events.
00404  * @author Tim Niemueller
00405  *
00406  * @fn FamListener::fam_event(const char *filename, unsigned int mask)
00407  * Event has been raised.
00408  * @param filename name of the file that triggered the event
00409  * @param mask mask indicating the event. Currently inotify event flags
00410  * are used, see inotify.h.
00411  *
00412  */
00413 
00414 /** Virtual empty destructor. */
00415 FamListener::~FamListener()
00416 {
00417 }
00418 
00419 } // end of namespace fawkes
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends