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 <cerrno>
00033 #  include <cstring>
00034 #endif
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   __regexes.clear();
00121 }
00122
00123 
00124 /** Destructor. */
00125 FileAlterationMonitor::~FileAlterationMonitor()
00126 {
00127   for (__rxit = __regexes.begin(); __rxit != __regexes.end(); ++__rxit) {
00128     regfree(*__rxit);
00129     free(*__rxit);
00130   }
00131
00132 #ifdef HAVE_INOTIFY
00133   for (__inotify_wit = __inotify_watches.begin(); __inotify_wit != __inotify_watches.end(); ++__inotify_wit) {
00134     inotify_rm_watch(__inotify_fd, __inotify_wit->first);
00135   }
00136   close(__inotify_fd);
00137   if ( __inotify_buf ) {
00138     free(__inotify_buf);
00139     __inotify_buf = NULL;
00140   }
00141 #endif
00142 }
00143
00144 
00145 /** Watch a directory.
00146  * This adds the given directory recursively to this FAM.
00147  * @param dirpath path to directory to add
00148  */
00149 void
00150 FileAlterationMonitor::watch_dir(const char *dirpath)
00151 {
00152 #ifdef HAVE_INOTIFY
00153   DIR *d = opendir(dirpath);
00154   if ( d == NULL ) {
00155     throw Exception(errno, "Failed to open dir %s", dirpath);
00156   }
00157
00158   uint32_t mask = IN_MODIFY | IN_MOVE | IN_CREATE | IN_DELETE | IN_DELETE_SELF;
00159   int iw;
00160
00161   //LibLogger::log_debug("FileAlterationMonitor", "Adding watch for %s", dirpath);
00162   if ( (iw = inotify_add_watch(__inotify_fd, dirpath, mask)) >= 0) {
00163     __inotify_watches[iw] = dirpath;
00164
00165     dirent de, *res;
00166     while ( (readdir_r(d, &de, &res) == 0) && (res != NULL) ) {
00167       std::string fp = std::string(dirpath) + "/" + de.d_name;
00168       struct stat st;
00169       if ( stat(fp.c_str(), &st) == 0 ) {
00170         if ( (de.d_name[0] != '.') && S_ISDIR(st.st_mode) ) {
00171           try {
00172             watch_dir(fp.c_str());
00173           } catch (Exception &e) {
00174             closedir(d);
00175             throw;
00176           }
00177         //} else {
00178           //LibLogger::log_debug("SkillerExecutionThread", "Skipping file %s", fp.c_str());       
00179         }
00180       } else {
00181         LibLogger::log_debug("FileAlterationMonitor",
00182                              "Skipping watch on %s, cannot stat (%s)",
00183                              fp.c_str(), strerror(errno));
00184       }
00185     }
00186   } else {
00187     throw Exception("FileAlterationMonitor",
00188                     "Cannot add watch for %s", dirpath);
00189   }
00190
00191   closedir(d);
00192 #endif
00193 }
00194
00195 
00196 /** Add a filter.
00197  * Filters are applied to path names that triggered an event. All
00198  * pathnames are checked against this regex and if any does not match
00199  * the event is not posted to listeners.
00200  * An example regular expression is
00201  * @code
00202  * ^[^.].*\\.lua$
00203  * @endcode
00204  * This regular expression matches to all files that does not start with
00205  * a dot and have an .lua ending.
00206  * @param regex regular expression to add
00207  */
00208 void
00209 FileAlterationMonitor::add_filter(const char *regex)
00210 {
00211   int regerr = 0;
00212   regex_t *rx = (regex_t *)malloc(sizeof(regex_t));
00213   if ( (regerr = regcomp(rx, regex, REG_EXTENDED)) != 0 ) {
00214     char errtmp[1024];
00215     regerror(regerr, rx, errtmp, sizeof(errtmp));
00216     free(rx);
00217     throw Exception("Failed to compile lua file regex: %s", errtmp);
00218   }
00219   __regexes.push_back_locked(rx);
00220 }
00221
00222 
00223 /** Add a listener.
00224  * @param listener listener to add
00225  */
00226 void
00227 FileAlterationMonitor::add_listener(FamListener *listener)
00228 {
00229   __listeners.push_back_locked(listener);
00230 }
00231
00232 
00233 /** Remove a listener.
00234  * @param listener listener to remove
00235  */
00236 void
00237 FileAlterationMonitor::remove_listener(FamListener *listener)
00238 {
00239   __listeners.remove_locked(listener);
00240 }
00241
00242 
00243 /** Process events.
00244  * Call this when you want file events to be processed.
00245  * @param timeout timeout in milliseconds to wait for an event, 0 to just check
00246  * and no wait, -1 to wait forever until an event is received
00247  */
00248 void
00249 FileAlterationMonitor::process_events(int timeout)
00250 {
00251 #ifdef HAVE_INOTIFY
00252   // Check for inotify events
00253   pollfd ipfd;
00254   ipfd.fd = __inotify_fd;
00255   ipfd.events = POLLIN;
00256   ipfd.revents = 0;
00257   int prv = poll(&ipfd, 1, timeout);
00258   if ( prv == -1 ) {
00259     LibLogger::log_error("FileAlterationMonitor",
00260                          "inotify poll failed: %s (%i)",
00261                          strerror(errno), errno);
00262   } else while ( prv > 0 ) {
00263     // Our fd has an event, we can read
00264     if ( ipfd.revents & POLLERR ) {
00265       LibLogger::log_error("FileAlterationMonitor", "inotify poll error");
00266     } else {
00267       // must be POLLIN
00268       int bytes = 0, i = 0;
00269       if ((bytes = read(__inotify_fd, __inotify_buf, __inotify_bufsize)) != -1) {
00270         while (i < bytes) {
00271           struct inotify_event *event = (struct inotify_event *) &__inotify_buf[i];
00272
00273           bool valid = true;
00274           if (! (event->mask & IN_ISDIR)) {
00275             for (__rxit = __regexes.begin(); __rxit != __regexes.end(); ++__rxit) {
00276               if (regexec(*__rxit, event->name, 0, NULL, 0) == REG_NOMATCH ) {
00277                 //LibLogger::log_debug("FileAlterationMonitor", "A regex did not match for %s", event->name);
00278                 valid = false;
00279                 break;
00280               }
00281             }
00282           }
00283
00284           /*
00285           if (event->mask & IN_MODIFY) {
00286             LibLogger::log_debug("FileAlterationMonitor", "%s has been modified", event->name);
00287             }
00288           if (event->mask & IN_MOVE) {
00289             LibLogger::log_debug("FileAlterationMonitor", "%s has been moved", event->name);
00290             }
00291           if (event->mask & IN_DELETE) {
00292             LibLogger::log_debug("FileAlterationMonitor", "%s has been deleted", event->name);
00293           }
00294           if (event->mask & IN_CREATE) {
00295             LibLogger::log_debug("FileAlterationMonitor", "%s has been created", event->name);
00296           }
00297           */
00298
00299           if ( valid ) {
00300             for (__lit = __listeners.begin(); __lit != __listeners.end(); ++__lit) {
00301               (*__lit)->fam_event(event->name, event->mask);
00302             }
00303           }
00304
00305           if (event->mask & IN_DELETE_SELF) {
00306             //LibLogger::log_debug("FileAlterationMonitor", "Watched %s has been deleted", event->name);
00307             __inotify_watches.erase(event->wd);
00308             inotify_rm_watch(__inotify_fd, event->wd);
00309           }
00310
00311           if (event->mask & IN_CREATE) {
00312             // Check if it is a directory, if it is, watch it
00313             std::string fp = __inotify_watches[event->wd] + "/" + event->name;
00314             if (  (event->mask & IN_ISDIR) && (event->name[0] != '.') ) {
00315               /*
00316               LibLogger::log_debug("FileAlterationMonitor",
00317                                    "Directory %s has been created, "
00318                                    "adding to watch list", event->name);
00319               */
00320               try {
00321                 watch_dir(fp.c_str());
00322               } catch (Exception &e) {
00323                 LibLogger::log_warn("FileAlterationMonitor", "Adding watch for %s failed, ignoring.", fp.c_str());
00324                 LibLogger::log_warn("FileAlterationMonitor", e);
00325               }
00326             }
00327           }
00328
00329           i += sizeof(struct inotify_event) + event->len;
00330         }
00331       } else {
00332         LibLogger::log_error("FileAlterationMonitor", "inotify failed to read any bytes");
00333       }
00334     }
00335
00336     prv = poll(&ipfd, 1, 0);
00337   }
00338 #else
00339   LibLogger::log_error("FileAlterationMonitor",
00340                        "inotify support not available, but "
00341                        "process_events() was called. Ignoring.");
00342 #endif
00343 }
00344
00345 
00346 /** @class FamListener <utils/system/fam.h>
00347  * File Alteration Monitor Listener.
00348  * Listener called by FileAlterationMonitor for events.
00349  * @author Tim Niemueller
00350  *
00351  * @fn FamListener::fam_event(const char *filename, unsigned int mask)
00352  * Event has been raised.
00353  * @param filename name of the file that triggered the event
00354  * @param mask mask indicating the event. Currently inotify event flags
00355  * are used, see inotify.h.
00356  *
00357  */
00358 
00359 /** Virtual empty destructor. */
00360 FamListener::~FamListener()
00361 {
00362 }
00363
00364 } // end of namespace fawkes