Fawkes API Fawkes Development Version
|
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