Skip to content
Find file
2099 lines (1939 sloc) 62.9 KB
// kate: replace-tabs off; space-indent off;
/**
* @mainpage libinotifytools
*
* libinotifytools is a small C library to simplify the use of Linux's inotify
* interface.
*
* @link inotifytools/inotifytools.h Documentation for the library's public
* interface.@endlink
*
* @link todo.html TODO list.@endlink
*/
#include "../../config.h"
#include "inotifytools/inotifytools.h"
#include "inotifytools_p.h"
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <dirent.h>
#include <time.h>
#include <regex.h>
#include <setjmp.h>
#include "inotifytools/inotify.h"
/**
* @file inotifytools/inotifytools.h
* @brief inotifytools library public interface.
* @author Rohan McGovern, \<rohan@mcgovern.id.au\>
*
* This library provides a thin layer on top of the basic inotify interface.
* The primary use is to easily set up watches on files, potentially many files
* at once, and read events without having to deal with low-level I/O. There
* are also several utility functions for inotify-related string formatting.
*
* To use this library, you must \c \#include the following headers accordingly:
* \li \c \<inotifytools/inotifytools.h\> - to use any functions declared in
* this file.
* \li \c \<inotifytools/inotify.h\> - to have the \c inotify_event type defined
* and the numeric IN_* event constants defined. If \c \<sys/inotify.h\>
* was present on your system at compile time, this header simply includes
* that. Otherwise it includes \c \<inotifytools/inotify-nosys.h\>.
*
* @section example Example
* This very simple program recursively watches the entire directory tree
* under its working directory for events, then prints them out with a
* timestamp.
* @include example.c
*
* @section events Events
*
* @note This section comes almost verbatim from the inotify(7) man page.
*
* @warning The information here applies to inotify in Linux 2.6.17. Older
* versions of Linux may not support all the events described here.
*
* The following numeric events can be specified to functions in inotifytools,
* and may be present in events returned through inotifytools:
*
* \li \c IN_ACCESS - File was accessed (read) \a *
* \li \c IN_ATTRIB - Metadata changed (permissions, timestamps,
* extended attributes, etc.) \a *
* \li \c IN_CLOSE_WRITE - File opened for writing was closed \a *
* \li \c IN_CLOSE_NOWRITE - File not opened for writing was closed \a *
* \li \c IN_CREATE - File/directory created in watched directory \a *
* \li \c IN_DELETE - File/directory deleted from watched directory \a *
* \li \c IN_DELETE_SELF - Watched file/directory was itself deleted
* \li \c IN_MODIFY - File was modified \a *
* \li \c IN_MOVE_SELF - Watched file/directory was itself moved
* \li \c IN_MOVED_FROM - File moved out of watched directory \a *
* \li \c IN_MOVED_TO - File moved into watched directory \a *
* \li \c IN_OPEN - File was opened \a *
*
* When monitoring a directory, the events marked with an asterisk \a * above
* can occur for files in the directory, in which case the name field in the
* returned inotify_event structure identifies the name of the file within the
* directory.
*
* The IN_ALL_EVENTS macro is defined as a bit mask of all of the above events.
*
* Two additional convenience macros are IN_MOVE, which equates to
* IN_MOVED_FROM|IN_MOVED_TO, and IN_CLOSE which equates to
* IN_CLOSE_WRITE|IN_CLOSE_NOWRITE.
*
* The following bitmasks can also be provided when creating a new watch:
*
* \li \c IN_DONT_FOLLOW - Don't dereference pathname if it is a symbolic link
* \li \c IN_MASK_ADD - Add (OR) events to watch mask for this pathname if
* it already exists (instead of replacing mask)
* \li \c IN_ONESHOT - Monitor pathname for one event, then remove from
* watch list
* \li \c IN_ONLYDIR - Only watch pathname if it is a directory
*
* The following bitmasks may occur in events generated by a watch:
*
* \li \c IN_IGNORED - Watch was removed explicitly
* (inotifytools_remove_watch_*) or automatically (file
* was deleted, or file system was unmounted)
* \li \c IN_ISDIR - Subject of this event is a directory
* \li \c IN_Q_OVERFLOW - Event queue overflowed (wd is -1 for this event)
* \li \c IN_UNMOUNT - File system containing watched object was unmounted
*
* @section TODO TODO list
*
* @todo Improve wd/filename mapping. Currently there is no explicit code for
* handling different filenames mapping to the same inode (and hence, wd).
* gamin's approach sounds good: let the user watch an inode using several
* different filenames, and when an event occurs on the inode, generate an
* event for each filename.
*/
#define MAX_EVENTS 4096
#define MAX_STRLEN 4096
#define INOTIFY_PROCDIR "/proc/sys/fs/inotify/"
#define WATCHES_SIZE_PATH INOTIFY_PROCDIR "max_user_watches"
#define QUEUE_SIZE_PATH INOTIFY_PROCDIR "max_queued_watches"
#define INSTANCES_PATH INOTIFY_PROCDIR "max_user_instances"
static int inotify_fd;
static unsigned num_access;
static unsigned num_modify;
static unsigned num_attrib;
static unsigned num_close_nowrite;
static unsigned num_close_write;
static unsigned num_open;
static unsigned num_move_self;
static unsigned num_moved_to;
static unsigned num_moved_from;
static unsigned num_create;
static unsigned num_delete;
static unsigned num_delete_self;
static unsigned num_unmount;
static unsigned num_total;
static int collect_stats = 0;
struct rbtree *tree_wd = 0;
struct rbtree *tree_filename = 0;
static int error = 0;
static int init = 0;
static char* timefmt = 0;
static regex_t* regex = 0;
/* 0: --exclude[i], 1: --include[i] */
static int invert_regexp = 0;
static int isdir( char const * path );
void record_stats( struct inotify_event const * event );
int onestr_to_event(char const * event);
/**
* @internal
* Assert that a condition evaluates to true, and optionally output a message
* if the assertion fails.
*
* @param cond Integer; if 0, assertion fails, otherwise assertion succeeds.
*
* @param mesg A human-readable error message shown if assertion fails.
*
* @section example Example
* @code
* int upper = 100, lower = 50;
* int input = get_user_input();
* niceassert( input <= upper && input >= lower,
* "input not in required range!");
* @endcode
*/
#define niceassert(cond,mesg) _niceassert((long)cond, __LINE__, __FILE__, \
#cond, mesg)
#define nasprintf(...) niceassert( -1 != asprintf(__VA_ARGS__), "out of memory")
/**
* @internal
* Assert that a condition evaluates to true, and optionally output a message
* if the assertion fails.
*
* You should use the niceassert() preprocessor macro instead.
*
* @param cond If 0, assertion fails, otherwise assertion succeeds.
*
* @param line Line number of source code where assertion is made.
*
* @param file Name of source file where assertion is made.
*
* @param condstr Stringified assertion expression.
*
* @param mesg A human-readable error message shown if assertion fails.
*/
static void _niceassert( long cond, int line, char const * file,
char const * condstr, char const * mesg ) {
if ( cond ) return;
if ( mesg ) {
fprintf(stderr, "%s:%d assertion ( %s ) failed: %s\n", file, line,
condstr, mesg );
}
else {
fprintf(stderr, "%s:%d assertion ( %s ) failed.\n", file, line, condstr);
}
}
/**
* @internal
* Construct a string from a character.
*
* @param ch A character.
*
* @return A string of length 1 consisting of the character @a ch. The string
* will be overwritten in subsequent calls.
*/
char * chrtostr(char ch) {
static char str[2] = { '\0', '\0' };
str[0] = ch;
return str;
}
/**
* @internal
*/
int read_num_from_file( char * filename, int * num ) {
FILE * file = fopen( filename, "r" );
if ( !file ) {
error = errno;
return 0;
}
if ( EOF == fscanf( file, "%d", num ) ) {
error = errno;
return 0;
}
niceassert( 0 == fclose( file ), 0 );
return 1;
}
int wd_compare(const void *d1, const void *d2, const void *config) {
if (!d1 || !d2) return d1 - d2;
return ((watch*)d1)->wd - ((watch*)d2)->wd;
}
int filename_compare(const void *d1, const void *d2, const void *config) {
if (!d1 || !d2) return d1 - d2;
return strcmp(((watch*)d1)->filename, ((watch*)d2)->filename);
}
/**
* @internal
*/
watch *watch_from_wd( int wd ) {
watch w;
w.wd = wd;
return (watch*)rbfind(&w, tree_wd);
}
/**
* @internal
*/
watch *watch_from_filename( char const *filename ) {
watch w;
w.filename = (char*)filename;
return (watch*)rbfind(&w, tree_filename);
}
/**
* Initialise inotify.
*
* You must call this function before using any function which adds or removes
* watches or attempts to access any information about watches.
*
* @return 1 on success, 0 on failure. On failure, the error can be
* obtained from inotifytools_error().
*/
int inotifytools_initialize() {
if (init) return 1;
error = 0;
// Try to initialise inotify
inotify_fd = inotify_init();
if (inotify_fd < 0) {
error = errno;
return 0;
}
collect_stats = 0;
init = 1;
tree_wd = rbinit(wd_compare, 0);
tree_filename = rbinit(filename_compare, 0);
timefmt = 0;
return 1;
}
/**
* @internal
*/
void destroy_watch(watch *w) {
if (w->filename) free(w->filename);
free(w);
}
/**
* @internal
*/
void cleanup_tree(const void *nodep,
const VISIT which,
const int depth, void* arg) {
if (which != endorder && which != leaf) return;
watch *w = (watch*)nodep;
destroy_watch(w);
}
/**
* Close inotify and free the memory used by inotifytools.
*
* If you call this function, you must call inotifytools_initialize()
* again before any other functions can be used.
*/
void inotifytools_cleanup() {
if (!init) return;
init = 0;
close(inotify_fd);
collect_stats = 0;
error = 0;
timefmt = 0;
if (regex) {
regfree(regex);
free(regex);
regex = 0;
}
rbwalk(tree_wd, cleanup_tree, 0);
rbdestroy(tree_wd); tree_wd = 0;
rbdestroy(tree_filename); tree_filename = 0;
}
/**
* @internal
*/
void empty_stats(const void *nodep,
const VISIT which,
const int depth, void *arg) {
if (which != endorder && which != leaf) return;
watch *w = (watch*)nodep;
w->hit_access = 0;
w->hit_modify = 0;
w->hit_attrib = 0;
w->hit_close_nowrite = 0;
w->hit_close_write = 0;
w->hit_open = 0;
w->hit_move_self = 0;
w->hit_moved_from = 0;
w->hit_moved_to = 0;
w->hit_create = 0;
w->hit_delete = 0;
w->hit_delete_self = 0;
w->hit_unmount = 0;
w->hit_total = 0;
}
/**
* @internal
*/
void replace_filename(const void *nodep,
const VISIT which,
const int depth, void *arg) {
if (which != endorder && which != leaf) return;
watch *w = (watch*)nodep;
char *old_name = ((char**)arg)[0];
char *new_name = ((char**)arg)[1];
int old_len = *((int*)&((char**)arg)[2]);
char *name;
if ( 0 == strncmp( old_name, w->filename, old_len ) ) {
nasprintf( &name, "%s%s", new_name, &(w->filename[old_len]) );
if (!strcmp( w->filename, new_name )) {
free(name);
} else {
rbdelete(w, tree_filename);
free( w->filename );
w->filename = name;
rbsearch(w, tree_filename);
}
}
}
/**
* @internal
*/
void get_num(const void *nodep,
const VISIT which,
const int depth, void *arg) {
if (which != endorder && which != leaf) return;
++(*((int*)arg));
}
/**
* Initialize or reset statistics.
*
* inotifytools_initialize() must be called before this function can
* be used.
*
* When this function is called, all subsequent events will be tallied.
* Statistics can then be obtained via the @a inotifytools_get_stat_* functions.
*
* After the first call, subsequent calls to this function will reset the
* event tallies to 0.
*/
void inotifytools_initialize_stats() {
niceassert( init, "inotifytools_initialize not called yet" );
// if already collecting stats, reset stats
if (collect_stats) {
rbwalk(tree_wd, empty_stats, 0);
}
num_access = 0;
num_modify = 0;
num_attrib = 0;
num_close_nowrite = 0;
num_close_write = 0;
num_open = 0;
num_move_self = 0;
num_moved_from = 0;
num_moved_to = 0;
num_create = 0;
num_delete = 0;
num_delete_self = 0;
num_unmount = 0;
num_total = 0;
collect_stats = 1;
}
/**
* Convert character separated events from string form to integer form
* (as in inotify.h).
*
* @param event a sequence of events in string form as defined in
* inotify.h without leading IN_ prefix (e.g., MODIFY,
* ATTRIB), separated by the @a sep character. Case
* insensitive. Can be a single event.
* Can be empty or NULL. See section \ref events.
*
* @param sep Character used to separate events. @a sep must not be
* a character in a-z, A-Z, or _.
*
* @return integer representing the mask specified by @a event, or 0
* if any string in @a event is empty or NULL, or -1 if
* any string in @a event does not match any event or
* @a sep is invalid.
*
* @section example Example
* @code
* char * eventstr = "MODIFY:CLOSE:CREATE";
* int eventnum = inotifytools_str_to_event_sep( eventstr, ':' );
* if ( eventnum == IN_MODIFY | IN_CLOSE | IN_CREATE ) {
* printf( "This code always gets executed!\n" );
* }
* @endcode
*/
int inotifytools_str_to_event_sep(char const * event, char sep) {
if ( strchr( "_" "abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ", sep ) ) {
return -1;
}
int ret, ret1, len;
char * event1, * event2;
char eventstr[4096];
ret = 0;
if ( !event || !event[0] ) return 0;
event1 = (char *)event;
event2 = strchr( event1, sep );
while ( event1 && event1[0] ) {
if ( event2 ) {
len = event2 - event1;
niceassert( len < 4096, "malformed event string (very long)" );
}
else {
len = strlen(event1);
}
if ( len > 4095 ) len = 4095;
strncpy( eventstr, event1, len );
eventstr[len] = 0;
ret1 = onestr_to_event( eventstr );
if ( 0 == ret1 || -1 == ret1 ) {
ret = ret1;
break;
}
ret |= ret1;
event1 = event2;
if ( event1 && event1[0] ) {
// jump over 'sep' character
++event1;
// if last character was 'sep'...
if ( !event1[0] ) return 0;
event2 = strchr( event1, sep );
}
}
return ret;
}
/**
* Convert comma-separated events from string form to integer form
* (as in inotify.h).
*
* @param event a sequence of events in string form as defined in
* inotify.h without leading IN_ prefix (e.g., MODIFY,
* ATTRIB), comma-separated. Case
* insensitive. Can be a single event.
* Can be empty or NULL. See section \ref events.
*
* @return integer representing the mask specified by @a event, or 0
* if any string in @a event is empty or NULL, or -1 if
* any string in @a event does not match any event.
*
* @section example Example
* @code
* char * eventstr = "MODIFY,CLOSE,CREATE";
* int eventnum = inotifytools_str_to_event( eventstr );
* if ( eventnum == IN_MODIFY | IN_CLOSE | IN_CREATE ) {
* printf( "This code always gets executed!\n" );
* }
* @endcode
*/
int inotifytools_str_to_event(char const * event) {
return inotifytools_str_to_event_sep( event, ',' );
}
/**
* @internal
* Convert a single event from string form to integer form (as in inotify.h).
*
* @param event event in string form as defined in inotify.h without
* leading IN_ prefix (e.g., MODIFY, ATTRIB). Case
* insensitive. Can be empty or NULL.
* @return integer representing the mask specified by 'event', or 0
* if @a event is empty or NULL, or -1 if string does not
* match any event.
*/
int onestr_to_event(char const * event)
{
static int ret;
ret = -1;
if ( !event || !event[0] )
ret = 0;
else if ( 0 == strcasecmp(event, "ACCESS") )
ret = IN_ACCESS;
else if ( 0 == strcasecmp(event, "MODIFY") )
ret = IN_MODIFY;
else if ( 0 == strcasecmp(event, "ATTRIB") )
ret = IN_ATTRIB;
else if ( 0 == strcasecmp(event, "CLOSE_WRITE") )
ret = IN_CLOSE_WRITE;
else if ( 0 == strcasecmp(event, "CLOSE_NOWRITE") )
ret = IN_CLOSE_NOWRITE;
else if ( 0 == strcasecmp(event, "OPEN") )
ret = IN_OPEN;
else if ( 0 == strcasecmp(event, "MOVED_FROM") )
ret = IN_MOVED_FROM;
else if ( 0 == strcasecmp(event, "MOVED_TO") )
ret = IN_MOVED_TO;
else if ( 0 == strcasecmp(event, "CREATE") )
ret = IN_CREATE;
else if ( 0 == strcasecmp(event, "DELETE") )
ret = IN_DELETE;
else if ( 0 == strcasecmp(event, "DELETE_SELF") )
ret = IN_DELETE_SELF;
else if ( 0 == strcasecmp(event, "UNMOUNT") )
ret = IN_UNMOUNT;
else if ( 0 == strcasecmp(event, "Q_OVERFLOW") )
ret = IN_Q_OVERFLOW;
else if ( 0 == strcasecmp(event, "IGNORED") )
ret = IN_IGNORED;
else if ( 0 == strcasecmp(event, "CLOSE") )
ret = IN_CLOSE;
else if ( 0 == strcasecmp(event, "MOVE_SELF") )
ret = IN_MOVE_SELF;
else if ( 0 == strcasecmp(event, "MOVE") )
ret = IN_MOVE;
else if ( 0 == strcasecmp(event, "ISDIR") )
ret = IN_ISDIR;
else if ( 0 == strcasecmp(event, "ONESHOT") )
ret = IN_ONESHOT;
else if ( 0 == strcasecmp(event, "ALL_EVENTS") )
ret = IN_ALL_EVENTS;
return ret;
}
/**
* Convert event from integer form to string form (as in inotify.h).
*
* The returned string is from static storage; subsequent calls to this function
* or inotifytools_event_to_str_sep() will overwrite it. Don't free() it and
* make a copy if you want to keep it.
*
* @param events OR'd event(s) in integer form as defined in inotify.h.
* See section \ref events.
*
* @return comma-separated string representing the event(s), in no
* particular order
*
* @section example Example
* @code
* int eventnum == IN_MODIFY | IN_CLOSE | IN_CREATE;
* char * eventstr = inotifytools_event_to_str( eventnum );
* printf( "%s\n", eventstr );
* // outputs something like MODIFY,CLOSE,CREATE but order not guaranteed.
* @endcode
*/
char * inotifytools_event_to_str(int events) {
return inotifytools_event_to_str_sep(events, ',');
}
/**
* Convert event from integer form to string form (as in inotify.h).
*
* The returned string is from static storage; subsequent calls to this function
* or inotifytools_event_to_str() will overwrite it. Don't free() it and
* make a copy if you want to keep it.
*
* @param events OR'd event(s) in integer form as defined in inotify.h
*
* @param sep character used to separate events
*
* @return @a sep separated string representing the event(s), in no
* particular order. If the integer is not made of OR'ed
* inotify events, the string returned will be a hexadecimal
* representation of the integer.
*
* @section example Example
* @code
* int eventnum == IN_MODIFY | IN_CLOSE | IN_CREATE;
* char * eventstr = inotifytools_event_to_str_sep( eventnum, '-' );
* printf( "%s\n", eventstr );
* // outputs something like MODIFY-CLOSE-CREATE but order not guaranteed.
* @endcode
*/
char * inotifytools_event_to_str_sep(int events, char sep)
{
static char ret[1024];
ret[0] = '\0';
ret[1] = '\0';
if ( IN_ACCESS & events ) {
strcat( ret, chrtostr(sep) );
strcat( ret, "ACCESS" );
}
if ( IN_MODIFY & events ) {
strcat( ret, chrtostr(sep) );
strcat( ret, "MODIFY" );
}
if ( IN_ATTRIB & events ) {
strcat( ret, chrtostr(sep) );
strcat( ret, "ATTRIB" );
}
if ( IN_CLOSE_WRITE & events ) {
strcat( ret, chrtostr(sep) );
strcat( ret, "CLOSE_WRITE" );
}
if ( IN_CLOSE_NOWRITE & events ) {
strcat( ret, chrtostr(sep) );
strcat( ret, "CLOSE_NOWRITE" );
}
if ( IN_OPEN & events ) {
strcat( ret, chrtostr(sep) );
strcat( ret, "OPEN" );
}
if ( IN_MOVED_FROM & events ) {
strcat( ret, chrtostr(sep) );
strcat( ret, "MOVED_FROM" );
}
if ( IN_MOVED_TO & events ) {
strcat( ret, chrtostr(sep) );
strcat( ret, "MOVED_TO" );
}
if ( IN_CREATE & events ) {
strcat( ret, chrtostr(sep) );
strcat( ret, "CREATE" );
}
if ( IN_DELETE & events ) {
strcat( ret, chrtostr(sep) );
strcat( ret, "DELETE" );
}
if ( IN_DELETE_SELF & events ) {
strcat( ret, chrtostr(sep) );
strcat( ret, "DELETE_SELF" );
}
if ( IN_UNMOUNT & events ) {
strcat( ret, chrtostr(sep) );
strcat( ret, "UNMOUNT" );
}
if ( IN_Q_OVERFLOW & events ) {
strcat( ret, chrtostr(sep) );
strcat( ret, "Q_OVERFLOW" );
}
if ( IN_IGNORED & events ) {
strcat( ret, chrtostr(sep) );
strcat( ret, "IGNORED" );
}
if ( IN_CLOSE & events ) {
strcat( ret, chrtostr(sep) );
strcat( ret, "CLOSE" );
}
if ( IN_MOVE_SELF & events ) {
strcat( ret, chrtostr(sep) );
strcat( ret, "MOVE_SELF" );
}
if ( IN_ISDIR & events ) {
strcat( ret, chrtostr(sep) );
strcat( ret, "ISDIR" );
}
if ( IN_ONESHOT & events ) {
strcat( ret, chrtostr(sep) );
strcat( ret, "ONESHOT" );
}
// Maybe we didn't match any... ?
if (ret[0] == '\0') {
niceassert( -1 != sprintf( ret, "%c0x%08x", sep, events ), 0 );
}
return &ret[1];
}
/**
* Get the filename used to establish a watch.
*
* inotifytools_initialize() must be called before this function can
* be used.
*
* @param wd watch descriptor.
*
* @return filename associated with watch descriptor @a wd, or NULL if @a wd
* is not associated with any filename.
*
* @note This always returns the filename which was used to establish a watch.
* This means the filename may be a relative path. If this isn't desired,
* then always use absolute paths when watching files.
* Also, this is not necessarily the filename which might have been used
* to cause an event on the file, since inotify is inode based and there
* can be many filenames mapping to a single inode.
* Finally, if a file is moved or renamed while being watched, the
* filename returned will still be the original name.
*/
char * inotifytools_filename_from_wd( int wd ) {
niceassert( init, "inotifytools_initialize not called yet" );
watch *w = watch_from_wd(wd);
if (!w)
return NULL;
return w->filename;
}
/**
* Get the watch descriptor for a particular filename.
*
* inotifytools_initialize() must be called before this function can
* be used.
*
* @param filename file name to find watch descriptor for.
*
* @return watch descriptor associated with filename, or -1 if @a filename is
* not associated with any watch descriptor.
*
* @note The filename specified must always be the original name used to
* establish the watch.
*/
int inotifytools_wd_from_filename( char const * filename ) {
niceassert( init, "inotifytools_initialize not called yet" );
watch *w = watch_from_filename(filename);
if (!w) return -1;
return w->wd;
}
/**
* Set the filename for a particular watch descriptor.
*
* This function should be used to update a filename when a file is known to
* have been moved or renamed. At the moment, libinotifytools does not
* automatically handle this situation.
*
* inotifytools_initialize() must be called before this function can
* be used.
*
* @param wd Watch descriptor.
*
* @param filename New filename.
*/
void inotifytools_set_filename_by_wd( int wd, char const * filename ) {
niceassert( init, "inotifytools_initialize not called yet" );
watch *w = watch_from_wd(wd);
if (!w) return;
if (w->filename) free(w->filename);
w->filename = strdup(filename);
}
/**
* Set the filename for one or more watches with a particular existing filename.
*
* This function should be used to update a filename when a file is known to
* have been moved or renamed. At the moment, libinotifytools does not
* automatically handle this situation.
*
* inotifytools_initialize() must be called before this function can
* be used.
*
* @param oldname Current filename.
*
* @param newname New filename.
*/
void inotifytools_set_filename_by_filename( char const * oldname,
char const * newname ) {
watch *w = watch_from_filename(oldname);
if (!w) return;
if (w->filename) free(w->filename);
w->filename = strdup(newname);
}
/**
* Replace a certain filename prefix on all watches.
*
* This function should be used to update filenames for an entire directory tree
* when a directory is known to have been moved or renamed. At the moment,
* libinotifytools does not automatically handle this situation.
*
* inotifytools_initialize() must be called before this function can
* be used.
*
* @param oldname Current filename prefix.
*
* @param newname New filename prefix.
*
* @section example Example
* @code
* // if /home/user1/original_dir is moved to /home/user2/new_dir, then to
* // update all watches:
* inotifytools_replace_filename( "/home/user1/original_dir",
* "/home/user2/new_dir" );
* @endcode
*/
void inotifytools_replace_filename( char const * oldname,
char const * newname ) {
if ( !oldname || !newname ) return;
char *names[2+sizeof(int)/sizeof(char*)];
names[0] = (char*)oldname;
names[1] = (char*)newname;
*((int*)&names[2]) = strlen(oldname);
rbwalk(tree_filename, replace_filename, (void*)names);
}
/**
* @internal
*/
int remove_inotify_watch(watch *w) {
error = 0;
int status = inotify_rm_watch( inotify_fd, w->wd );
if ( status < 0 ) {
fprintf(stderr, "Failed to remove watch on %s: %s\n", w->filename,
strerror(status) );
error = status;
return 0;
}
return 1;
}
/**
* @internal
*/
watch *create_watch(int wd, char *filename) {
if ( wd <= 0 || !filename) return 0;
watch *w = (watch*)calloc(1, sizeof(watch));
w->wd = wd;
w->filename = strdup(filename);
rbsearch(w, tree_wd);
rbsearch(w, tree_filename);
}
/**
* Remove a watch on a file specified by watch descriptor.
*
* inotifytools_initialize() must be called before this function can
* be used.
*
* @param wd Watch descriptor of watch to be removed.
*
* @return 1 on success, 0 on failure. If the given watch doesn't exist,
* returns 1. On failure, the error can be
* obtained from inotifytools_error().
*/
int inotifytools_remove_watch_by_wd( int wd ) {
niceassert( init, "inotifytools_initialize not called yet" );
watch *w = watch_from_wd(wd);
if (!w) return 1;
if (!remove_inotify_watch(w)) return 0;
rbdelete(w, tree_wd);
rbdelete(w, tree_filename);
destroy_watch(w);
return 1;
}
/**
* Remove a watch on a file specified by filename.
*
* @param filename Name of file on which watch should be removed.
*
* @return 1 on success, 0 on failure. On failure, the error can be
* obtained from inotifytools_error().
*
* @note The filename specified must always be the original name used to
* establish the watch.
*/
int inotifytools_remove_watch_by_filename( char const * filename ) {
niceassert( init, "inotifytools_initialize not called yet" );
watch *w = watch_from_filename(filename);
if (!w) return 1;
if (!remove_inotify_watch(w)) return 0;
rbdelete(w, tree_wd);
rbdelete(w, tree_filename);
destroy_watch(w);
return 1;
}
/**
* Set up a watch on a file.
*
* @param filename Absolute or relative path of file to watch.
*
* @param events bitwise ORed inotify events to watch for. See section
* \ref events.
*
* @return 1 on success, 0 on failure. On failure, the error can be
* obtained from inotifytools_error().
*/
int inotifytools_watch_file( char const * filename, int events ) {
static char const * filenames[2];
filenames[0] = filename;
filenames[1] = NULL;
return inotifytools_watch_files( filenames, events );
}
/**
* Set up a watch on a list of files.
*
* inotifytools_initialize() must be called before this function can
* be used.
*
* @param filenames null-terminated array of absolute or relative paths of
* files to watch.
*
* @param events bitwise OR'ed inotify events to watch for. See section
* \ref events.
*
* @return 1 on success, 0 on failure. On failure, the error can be
* obtained from inotifytools_error().
*/
int inotifytools_watch_files( char const * filenames[], int events ) {
niceassert( init, "inotifytools_initialize not called yet" );
error = 0;
static int i;
for ( i = 0; filenames[i]; ++i ) {
static int wd;
wd = inotify_add_watch( inotify_fd, filenames[i], events );
if ( wd < 0 ) {
if ( wd == -1 ) {
error = errno;
return 0;
} // if ( wd == -1 )
else {
fprintf( stderr, "Failed to watch %s: returned wd was %d "
"(expected -1 or >0 )", filenames[i], wd );
// no appropriate value for error
return 0;
} // else
} // if ( wd < 0 )
char *filename;
// Always end filename with / if it is a directory
if ( !isdir(filenames[i])
|| filenames[i][strlen(filenames[i])-1] == '/') {
filename = strdup(filenames[i]);
}
else {
nasprintf( &filename, "%s/", filenames[i] );
}
create_watch(wd, filename);
free(filename);
} // for
return 1;
}
/**
* Get the next inotify event to occur.
*
* inotifytools_initialize() must be called before this function can
* be used.
*
* @param timeout maximum amount of time, in seconds, to wait for an event.
* If @a timeout is non-negative, the function is non-blocking.
* If @a timeout is negative, the function will block until an
* event occurs.
*
* @return pointer to an inotify event, or NULL if function timed out before
* an event occurred. The event is located in static storage and it
* may be overwritten in subsequent calls; do not call free() on it,
* and make a copy if you want to keep it.
*
* @note Your program should call this function or
* inotifytools_next_events() frequently; between calls to this function,
* inotify events will be queued in the kernel, and eventually the queue
* will overflow and you will miss some events.
*
* @note If the function inotifytools_ignore_events_by_regex() has been called
* with a non-NULL parameter, this function will not return on events
* which match the regular expression passed to that function. However,
* the @a timeout period begins again each time a matching event occurs.
*/
struct inotify_event * inotifytools_next_event( long int timeout ) {
return inotifytools_next_events( timeout, 1 );
}
/**
* Get the next inotify events to occur.
*
* inotifytools_initialize() must be called before this function can
* be used.
*
* @param timeout maximum amount of time, in seconds, to wait for an event.
* If @a timeout is non-negative, the function is non-blocking.
* If @a timeout is negative, the function will block until an
* event occurs.
*
* @param num_events approximate number of inotify events to wait for until
* this function returns. Use this for buffering reads to
* inotify if you expect to receive large amounts of events.
* You are NOT guaranteed that this number of events will
* actually be read; instead, you are guaranteed that the
* number of bytes read from inotify is
* @a num_events * sizeof(struct inotify_event). Obviously
* the larger this number is, the greater the latency between
* when an event occurs and when you'll know about it.
* May not be larger than 4096.
*
* @return pointer to an inotify event, or NULL if function timed out before
* an event occurred or @a num_events < 1. The event is located in
* static storage and it may be overwritten in subsequent calls; do not
* call free() on it, and make a copy if you want to keep it.
* When @a num_events is greater than 1, this will return a pointer to
* the first event only, and you MUST call this function again to
* get pointers to subsequent events; don't try to add to the pointer
* to find the next events or you will run into trouble.
*
* @note You may actually get different events with different values of
* @a num_events. This is because inotify does some in-kernel filtering
* of duplicate events, meaning some duplicate events will not be
* reported if @a num_events > 1. For some purposes this is fine, but
* for others (such as gathering accurate statistics on numbers of event
* occurrences) you must call this function with @a num_events = 1, or
* simply use inotifytools_next_event().
*
* @note Your program should call this function frequently; between calls to this
* function, inotify events will be queued in the kernel, and eventually
* the queue will overflow and you will miss some events.
*
* @note If the function inotifytools_ignore_events_by_regex() has been called
* with a non-NULL parameter, this function will not return on events
* which match the regular expression passed to that function. However,
* the @a timeout period begins again each time a matching event occurs.
*/
struct inotify_event * inotifytools_next_events( long int timeout, int num_events ) {
niceassert( init, "inotifytools_initialize not called yet" );
niceassert( num_events <= MAX_EVENTS, "too many events requested" );
if ( num_events < 1 ) return NULL;
static struct inotify_event event[MAX_EVENTS];
static struct inotify_event * ret;
static int first_byte = 0;
static ssize_t bytes;
static jmp_buf jmp;
static char match_name[MAX_STRLEN];
#define RETURN(A) {\
if (regex) {\
inotifytools_snprintf(match_name, MAX_STRLEN, A, "%w%f");\
if (0 == regexec(regex, match_name, 0, 0, 0)) {\
if (!invert_regexp)\
longjmp(jmp,0);\
} else {\
if (invert_regexp)\
longjmp(jmp,0);\
}\
}\
if ( collect_stats ) {\
record_stats( A );\
}\
return A;\
}
setjmp(jmp);
error = 0;
// first_byte is index into event buffer
if ( first_byte != 0
&& first_byte <= (int)(bytes - sizeof(struct inotify_event)) ) {
ret = (struct inotify_event *)((char *)&event[0] + first_byte);
first_byte += sizeof(struct inotify_event) + ret->len;
// if the pointer to the next event exactly hits end of bytes read,
// that's good. next time we're called, we'll read.
if ( first_byte == bytes ) {
first_byte = 0;
}
else if ( first_byte > bytes ) {
// oh... no. this can't be happening. An incomplete event.
// Copy what we currently have into first element, call self to
// read remainder.
// oh, and they BETTER NOT overlap.
// Boy I hope this code works.
// But I think this can never happen due to how inotify is written.
niceassert( (long)((char *)&event[0] +
sizeof(struct inotify_event) +
event[0].len) <= (long)ret,
"extremely unlucky user, death imminent" );
// how much of the event do we have?
bytes = (char *)&event[0] + bytes - (char *)ret;
memcpy( &event[0], ret, bytes );
return inotifytools_next_events( timeout, num_events );
}
RETURN(ret);
}
else if ( first_byte == 0 ) {
bytes = 0;
}
static ssize_t this_bytes;
static unsigned int bytes_to_read;
static int rc;
static fd_set read_fds;
static struct timeval read_timeout;
read_timeout.tv_sec = timeout;
read_timeout.tv_usec = 0;
static struct timeval * read_timeout_ptr;
read_timeout_ptr = ( timeout < 0 ? NULL : &read_timeout );
FD_ZERO(&read_fds);
FD_SET(inotify_fd, &read_fds);
rc = select(inotify_fd + 1, &read_fds,
NULL, NULL, read_timeout_ptr);
if ( rc < 0 ) {
// error
error = errno;
return NULL;
}
else if ( rc == 0 ) {
// timeout
return NULL;
}
// wait until we have enough bytes to read
do {
rc = ioctl( inotify_fd, FIONREAD, &bytes_to_read );
} while ( !rc &&
bytes_to_read < sizeof(struct inotify_event)*num_events );
if ( rc == -1 ) {
error = errno;
return NULL;
}
this_bytes = read(inotify_fd, &event[0] + bytes,
sizeof(struct inotify_event)*MAX_EVENTS - bytes);
if ( this_bytes < 0 ) {
error = errno;
return NULL;
}
if ( this_bytes == 0 ) {
fprintf(stderr, "Inotify reported end-of-file. Possibly too many "
"events occurred at once.\n");
return NULL;
}
bytes += this_bytes;
ret = &event[0];
first_byte = sizeof(struct inotify_event) + ret->len;
niceassert( first_byte <= bytes, "ridiculously long filename, things will "
"almost certainly screw up." );
if ( first_byte == bytes ) {
first_byte = 0;
}
RETURN(ret);
#undef RETURN
}
/**
* Set up recursive watches on an entire directory tree.
*
* inotifytools_initialize() must be called before this function can
* be used.
*
* @param path path of directory or file to watch. If the path is a directory,
* every subdirectory will also be watched for the same events up
* to the maximum readable depth. If the path is a file, the file
* is watched exactly as if inotifytools_watch_file() were used.
*
* @param events Inotify events to watch for. See section \ref events.
*
* @return 1 on success, 0 on failure. On failure, the error can be
* obtained from inotifytools_error(). Note that some errors on
* subdirectories will be ignored; for example, if you watch a directory
* tree which contains some directories which you do not have access to,
* those directories will not be watched, but this function will still
* return 1 if no other errors occur.
*
* @note This function does not attempt to work atomically. If you use this
* function to watch a directory tree and files or directories are being
* created or removed within that directory tree, there are no guarantees
* as to whether or not those files will be watched.
*/
int inotifytools_watch_recursively( char const * path, int events ) {
return inotifytools_watch_recursively_with_exclude( path, events, 0 );
}
/**
* Set up recursive watches on an entire directory tree, optionally excluding
* some directories.
*
* inotifytools_initialize() must be called before this function can
* be used.
*
* @author UH
*
* @param path path of directory or file to watch. If the path is a directory,
* every subdirectory will also be watched for the same events up
* to the maximum readable depth. If the path is a file, the file
* is watched exactly as if inotifytools_watch_file() were used.
*
* @param exclude_list NULL terminated path list of directories not to watch.
* Can be NULL if no paths are to be excluded.
* Directories may or may not include a trailing '/'.
*
* @param events Inotify events to watch for. See section \ref events.
*
* @return 1 on success, 0 on failure. On failure, the error can be
* obtained from inotifytools_error(). Note that some errors on
* subdirectories will be ignored; for example, if you watch a directory
* tree which contains some directories which you do not have access to,
* those directories will not be watched, but this function will still
* return 1 if no other errors occur.
*
* @note This function does not attempt to work atomically. If you use this
* function to watch a directory tree and files or directories are being
* created or removed within that directory tree, there are no guarantees
* as to whether or not those files will be watched.
*/
int inotifytools_watch_recursively_with_exclude( char const * path, int events,
char const ** exclude_list ) {
niceassert( init, "inotifytools_initialize not called yet" );
DIR * dir;
char * my_path;
error = 0;
dir = opendir( path );
if ( !dir ) {
// If not a directory, don't need to do anything special
if ( errno == ENOTDIR ) {
return inotifytools_watch_file( path, events );
}
else {
error = errno;
return 0;
}
}
if ( path[strlen(path)-1] != '/' ) {
nasprintf( &my_path, "%s/", path );
}
else {
my_path = (char *)path;
}
static struct dirent * ent;
char * next_file;
static struct stat64 my_stat;
ent = readdir( dir );
// Watch each directory within this directory
while ( ent ) {
if ( (0 != strcmp( ent->d_name, "." )) &&
(0 != strcmp( ent->d_name, ".." )) ) {
nasprintf(&next_file,"%s%s", my_path, ent->d_name);
if ( -1 == lstat64( next_file, &my_stat ) ) {
error = errno;
free( next_file );
if ( errno != EACCES ) {
error = errno;
if ( my_path != path ) free( my_path );
closedir( dir );
return 0;
}
}
else if ( S_ISDIR( my_stat.st_mode ) &&
!S_ISLNK( my_stat.st_mode )) {
free( next_file );
nasprintf(&next_file,"%s%s/", my_path, ent->d_name);
static unsigned int no_watch;
static char const ** exclude_entry;
no_watch = 0;
for (exclude_entry = exclude_list;
exclude_entry && *exclude_entry && !no_watch;
++exclude_entry) {
static int exclude_length;
exclude_length = strlen(*exclude_entry);
if ((*exclude_entry)[exclude_length-1] == '/') {
--exclude_length;
}
if ( strlen(next_file) == (unsigned)(exclude_length + 1) &&
!strncmp(*exclude_entry, next_file, exclude_length)) {
// directory found in exclude list
no_watch = 1;
}
}
if (!no_watch) {
static int status;
status = inotifytools_watch_recursively_with_exclude(
next_file,
events,
exclude_list );
// For some errors, we will continue.
if ( !status && (EACCES != error) && (ENOENT != error) &&
(ELOOP != error) ) {
free( next_file );
if ( my_path != path ) free( my_path );
closedir( dir );
return 0;
}
} // if !no_watch
free( next_file );
} // if isdir and not islnk
else {
free( next_file );
}
}
ent = readdir( dir );
error = 0;
}
closedir( dir );
int ret = inotifytools_watch_file( my_path, events );
if ( my_path != path ) free( my_path );
return ret;
}
/**
* @internal
*/
void record_stats( struct inotify_event const * event ) {
if (!event) return;
watch *w = watch_from_wd(event->wd);
if (!w) return;
if ( IN_ACCESS & event->mask ) {
++w->hit_access;
++num_access;
}
if ( IN_MODIFY & event->mask ) {
++w->hit_modify;
++num_modify;
}
if ( IN_ATTRIB & event->mask ) {
++w->hit_attrib;
++num_attrib;
}
if ( IN_CLOSE_WRITE & event->mask ) {
++w->hit_close_write;
++num_close_write;
}
if ( IN_CLOSE_NOWRITE & event->mask ) {
++w->hit_close_nowrite;
++num_close_nowrite;
}
if ( IN_OPEN & event->mask ) {
++w->hit_open;
++num_open;
}
if ( IN_MOVED_FROM & event->mask ) {
++w->hit_moved_from;
++num_moved_from;
}
if ( IN_MOVED_TO & event->mask ) {
++w->hit_moved_to;
++num_moved_to;
}
if ( IN_CREATE & event->mask ) {
++w->hit_create;
++num_create;
}
if ( IN_DELETE & event->mask ) {
++w->hit_delete;
++num_delete;
}
if ( IN_DELETE_SELF & event->mask ) {
++w->hit_delete_self;
++num_delete_self;
}
if ( IN_UNMOUNT & event->mask ) {
++w->hit_unmount;
++num_unmount;
}
if ( IN_MOVE_SELF & event->mask ) {
++w->hit_move_self;
++num_move_self;
}
++w->hit_total;
++num_total;
}
int *stat_ptr(watch *w, int event)
{
if ( IN_ACCESS == event )
return &w->hit_access;
if ( IN_MODIFY == event )
return &w->hit_modify;
if ( IN_ATTRIB == event )
return &w->hit_attrib;
if ( IN_CLOSE_WRITE == event )
return &w->hit_close_write;
if ( IN_CLOSE_NOWRITE == event )
return &w->hit_close_nowrite;
if ( IN_OPEN == event )
return &w->hit_open;
if ( IN_MOVED_FROM == event )
return &w->hit_moved_from;
if ( IN_MOVED_TO == event )
return &w->hit_moved_to;
if ( IN_CREATE == event )
return &w->hit_create;
if ( IN_DELETE == event )
return &w->hit_delete;
if ( IN_DELETE_SELF == event )
return &w->hit_delete_self;
if ( IN_UNMOUNT == event )
return &w->hit_unmount;
if ( IN_MOVE_SELF == event )
return &w->hit_move_self;
if ( 0 == event )
return &w->hit_total;
return 0;
}
/**
* Get statistics by a particular watch descriptor.
*
* inotifytools_initialize_stats() must be called before this function can
* be used.
*
* @param wd watch descriptor to get stats for.
*
* @param event a single inotify event to get statistics for, or 0 for event
* total. See section \ref events.
*
* @return the number of times the event specified by @a event has occurred on
* the watch descriptor specified by @a wd since stats collection was
* enabled, or -1 if @a event or @a wd are invalid.
*/
int inotifytools_get_stat_by_wd( int wd, int event ) {
if (!collect_stats) return -1;
watch *w = watch_from_wd(wd);
if (!w) return -1;
int *i = stat_ptr(w, event);
if (!i) return -1;
return *i;
}
/**
* Get statistics aggregated across all watches.
*
* inotifytools_initialize_stats() must be called before this function can
* be used.
*
* @param event a single inotify event to get statistics for, or 0 for event
* total. See section \ref events.
*
* @return the number of times the event specified by @a event has occurred over
* all watches since stats collection was enabled, or -1 if @a event
* is not a valid event.
*/
int inotifytools_get_stat_total( int event ) {
if (!collect_stats) return -1;
if ( IN_ACCESS == event )
return num_access;
if ( IN_MODIFY == event )
return num_modify;
if ( IN_ATTRIB == event )
return num_attrib;
if ( IN_CLOSE_WRITE == event )
return num_close_write;
if ( IN_CLOSE_NOWRITE == event )
return num_close_nowrite;
if ( IN_OPEN == event )
return num_open;
if ( IN_MOVED_FROM == event )
return num_moved_from;
if ( IN_MOVED_TO == event )
return num_moved_to;
if ( IN_CREATE == event )
return num_create;
if ( IN_DELETE == event )
return num_delete;
if ( IN_DELETE_SELF == event )
return num_delete_self;
if ( IN_UNMOUNT == event )
return num_unmount;
if ( IN_MOVE_SELF == event )
return num_move_self;
if ( 0 == event )
return num_total;
return -1;
}
/**
* Get statistics by a particular filename.
*
* inotifytools_initialize_stats() must be called before this function can
* be used.
*
* @param filename name of file to get stats for.
*
* @param event a single inotify event to get statistics for, or 0 for event
* total. See section \ref events.
*
* @return the number of times the event specified by @a event has occurred on
* the file specified by @a filename since stats collection was
* enabled, or -1 if the file is not being watched or @a event is
* invalid.
*
* @note The filename specified must always be the original name used to
* establish the watch.
*/
int inotifytools_get_stat_by_filename( char const * filename,
int event ) {
return inotifytools_get_stat_by_wd( inotifytools_wd_from_filename(
filename ), event );
}
/**
* Get the last error which occurred.
*
* When a function fails, call this to find out why. The returned value is
* a typical @a errno value, the meaning of which depends on context. For
* example, if inotifytools_watch_file() fails because you attempt to watch
* a file which doesn't exist, this function will return @a ENOENT.
*
* @return an error code.
*/
int inotifytools_error() {
return error;
}
/**
* @internal
*/
static int isdir( char const * path ) {
static struct stat64 my_stat;
if ( -1 == lstat64( path, &my_stat ) ) {
if (errno == ENOENT) return 0;
fprintf(stderr, "Stat failed on %s: %s\n", path, strerror(errno));
return 0;
}
return S_ISDIR( my_stat.st_mode ) && !S_ISLNK( my_stat.st_mode );
}
/**
* Get the number of watches set up through libinotifytools.
*
* @return number of watches set up by inotifytools_watch_file(),
* inotifytools_watch_files() and inotifytools_watch_recursively().
*/
int inotifytools_get_num_watches() {
int ret = 0;
rbwalk(tree_filename, get_num, (void*)&ret);
return ret;
}
/**
* Print a string to standard out using an inotify_event and a printf-like
* syntax.
* The string written will only ever be up to 4096 characters in length.
*
* @param event the event to use to construct a string.
*
* @param fmt the format string used to construct a string.
*
* @return number of characters written, or -1 if an error occurs.
*
* @section syntax Format string syntax
* The following tokens will be replaced with the specified string:
* \li \c \%w - This will be replaced with the name of the Watched file on
* which an event occurred.
* \li \c \%f - When an event occurs within a directory, this will be replaced
* with the name of the File which caused the event to occur.
* Otherwise, this will be replaced with an empty string.
* \li \c \%e - Replaced with the Event(s) which occurred, comma-separated.
* \li \c \%Xe - Replaced with the Event(s) which occurred, separated by
* whichever character is in the place of `X'.
* \li \c \%T - Replaced by the current Time in the format specified by the
* string previously passed to inotifytools_set_printf_timefmt(),
* or replaced with an empty string if that function has never
* been called.
*
* @section example Example
* @code
* // suppose this is the only file watched.
* inotifytools_watch_file( "mydir/", IN_CLOSE );
*
* // wait until an event occurs
* struct inotify_event * event = inotifytools_next_event( -1 );
*
* inotifytools_printf(stderr, event, "in %w, file %f had event(s): %.e\n");
* // suppose the file 'myfile' in mydir was read from and closed. Then,
* // this prints to standard out something like:
* // "in mydir/, file myfile had event(s): CLOSE_NOWRITE.CLOSE.ISDIR\n"
* @endcode
*/
int inotifytools_printf( struct inotify_event* event, char* fmt ) {
return inotifytools_fprintf( stdout, event, fmt );
}
/**
* Print a string to a file using an inotify_event and a printf-like syntax.
* The string written will only ever be up to 4096 characters in length.
*
* @param file file to print to
*
* @param event the event to use to construct a string.
*
* @param fmt the format string used to construct a string.
*
* @return number of characters written, or -1 if an error occurs.
*
* @section syntax Format string syntax
* The following tokens will be replaced with the specified string:
* \li \c \%w - This will be replaced with the name of the Watched file on
* which an event occurred.
* \li \c \%f - When an event occurs within a directory, this will be replaced
* with the name of the File which caused the event to occur.
* Otherwise, this will be replaced with an empty string.
* \li \c \%e - Replaced with the Event(s) which occurred, comma-separated.
* \li \c \%Xe - Replaced with the Event(s) which occurred, separated by
* whichever character is in the place of `X'.
* \li \c \%T - Replaced by the current Time in the format specified by the
* string previously passed to inotifytools_set_printf_timefmt(),
* or replaced with an empty string if that function has never
* been called.
*
* @section example Example
* @code
* // suppose this is the only file watched.
* inotifytools_watch_file( "mydir/", IN_CLOSE );
*
* // wait until an event occurs
* struct inotify_event * event = inotifytools_next_event( -1 );
*
* inotifytools_fprintf(stderr, event, "in %w, file %f had event(s): %.e\n");
* // suppose the file 'myfile' in mydir was read from and closed. Then,
* // this prints to standard error something like:
* // "in mydir/, file myfile had event(s): CLOSE_NOWRITE.CLOSE.ISDIR\n"
* @endcode
*/
int inotifytools_fprintf( FILE* file, struct inotify_event* event, char* fmt ) {
static char out[MAX_STRLEN+1];
static int ret;
ret = inotifytools_sprintf( out, event, fmt );
if ( -1 != ret ) fprintf( file, "%s", out );
return ret;
}
/**
* Construct a string using an inotify_event and a printf-like syntax.
* The string can only ever be up to 4096 characters in length.
*
* This function will keep writing until it reaches 4096 characters. If your
* allocated array is not large enough to hold the entire string, your program
* may crash.
* inotifytools_snprintf() is safer and you should use it where possible.
*
* @param out location in which to store string.
*
* @param event the event to use to construct a string.
*
* @param fmt the format string used to construct a string.
*
* @return number of characters written, or -1 if an error occurs.
*
* @section syntax Format string syntax
* The following tokens will be replaced with the specified string:
* \li \c \%w - This will be replaced with the name of the Watched file on
* which an event occurred.
* \li \c \%f - When an event occurs within a directory, this will be replaced
* with the name of the File which caused the event to occur.
* Otherwise, this will be replaced with an empty string.
* \li \c \%e - Replaced with the Event(s) which occurred, comma-separated.
* \li \c \%Xe - Replaced with the Event(s) which occurred, separated by
* whichever character is in the place of `X'.
* \li \c \%T - Replaced by the current Time in the format specified by the
* string previously passed to inotifytools_set_printf_timefmt(),
* or replaced with an empty string if that function has never
* been called.
*
* @section example Example
* @code
* // suppose this is the only file watched.
* inotifytools_watch_file( "mydir/", IN_CLOSE );
*
* // wait until an event occurs
* struct inotify_event * event = inotifytools_next_event( -1 );
*
* char mystring[1024];
* // hope this doesn't crash - if filename is really long, might not fit into
* // mystring!
* inotifytools_sprintf(mystring, event, "in %w, file %f had event(s): %.e\n");
* printf( mystring );
* // suppose the file 'myfile' in mydir was written to and closed. Then,
* // this prints something like:
* // "in mydir/, file myfile had event(s): CLOSE_WRITE.CLOSE.ISDIR\n"
* @endcode
*/
int inotifytools_sprintf( char * out, struct inotify_event* event, char* fmt ) {
return inotifytools_snprintf( out, MAX_STRLEN, event, fmt );
}
/**
* Construct a string using an inotify_event and a printf-like syntax.
* The string can only ever be up to 4096 characters in length.
*
* @param out location in which to store string.
*
* @param size maximum amount of characters to write.
*
* @param event the event to use to construct a string.
*
* @param fmt the format string used to construct a string.
*
* @return number of characters written, or -1 if an error occurs.
*
* @section syntax Format string syntax
* The following tokens will be replaced with the specified string:
* \li \c \%w - This will be replaced with the name of the Watched file on
* which an event occurred.
* \li \c \%f - When an event occurs within a directory, this will be replaced
* with the name of the File which caused the event to occur.
* Otherwise, this will be replaced with an empty string.
* \li \c \%e - Replaced with the Event(s) which occurred, comma-separated.
* \li \c \%Xe - Replaced with the Event(s) which occurred, separated by
* whichever character is in the place of `X'.
* \li \c \%T - Replaced by the current Time in the format specified by the
* string previously passed to inotifytools_set_printf_timefmt(),
* or replaced with an empty string if that function has never
* been called.
*
* @section example Example
* @code
* // suppose this is the only file watched.
* inotifytools_watch_file( "mydir/", IN_CLOSE );
*
* // wait until an event occurs
* struct inotify_event * event = inotifytools_next_event( -1 );
*
* char mystring[1024];
* inotifytools_snprintf( mystring, 1024, event,
* "in %w, file %f had event(s): %.e\n" );
* printf( mystring );
* // suppose the file 'myfile' in mydir was written to and closed. Then,
* // this prints something like:
* // "in mydir/, file myfile had event(s): CLOSE_WRITE.CLOSE.ISDIR\n"
* @endcode
*/
int inotifytools_snprintf( char * out, int size,
struct inotify_event* event, char* fmt ) {
static char * filename, * eventname, * eventstr;
static unsigned int i, ind;
static char ch1;
static char timestr[MAX_STRLEN];
static time_t now;
if ( event->len > 0 ) {
eventname = event->name;
}
else {
eventname = NULL;
}
filename = inotifytools_filename_from_wd( event->wd );
if ( !fmt || 0 == strlen(fmt) ) {
error = EINVAL;
return -1;
}
if ( strlen(fmt) > MAX_STRLEN || size > MAX_STRLEN) {
error = EMSGSIZE;
return -1;
}
ind = 0;
for ( i = 0; i < strlen(fmt) &&
(int)ind < size - 1; ++i ) {
if ( fmt[i] != '%' ) {
out[ind++] = fmt[i];
continue;
}
if ( i == strlen(fmt) - 1 ) {
// last character is %, invalid
error = EINVAL;
return ind;
}
ch1 = fmt[i+1];
if ( ch1 == '%' ) {
out[ind++] = '%';
++i;
continue;
}
if ( ch1 == 'w' ) {
if ( filename ) {
strncpy( &out[ind], filename, size - ind );
ind += strlen(filename);
}
++i;
continue;
}
if ( ch1 == 'f' ) {
if ( eventname ) {
strncpy( &out[ind], eventname, size - ind );
ind += strlen(eventname);
}
++i;
continue;
}
if ( ch1 == 'e' ) {
eventstr = inotifytools_event_to_str( event->mask );
strncpy( &out[ind], eventstr, size - ind );
ind += strlen(eventstr);
++i;
continue;
}
if ( ch1 == 'T' ) {
if ( timefmt ) {
now = time(0);
if ( 0 >= strftime( timestr, MAX_STRLEN-1, timefmt,
localtime( &now ) ) ) {
// time format probably invalid
error = EINVAL;
return ind;
}
}
else {
timestr[0] = 0;
}
strncpy( &out[ind], timestr, size - ind );
ind += strlen(timestr);
++i;
continue;
}
// Check if next char in fmt is e
if ( i < strlen(fmt) - 2 && fmt[i+2] == 'e' ) {
eventstr = inotifytools_event_to_str_sep( event->mask, ch1 );
strncpy( &out[ind], eventstr, size - ind );
ind += strlen(eventstr);
i += 2;
continue;
}
// OK, this wasn't a special format character, just output it as normal
if ( ind < MAX_STRLEN ) out[ind++] = '%';
if ( ind < MAX_STRLEN ) out[ind++] = ch1;
++i;
}
out[ind] = 0;
return ind - 1;
}
/**
* Set time format for printf functions.
*
* @param fmt A format string valid for use with strftime, or NULL. If NULL,
* time substitutions will no longer be made in printf functions.
* Note that this format string is not validated at all; using an
* incorrect format string will cause the printf functions to give
* incorrect results.
*/
void inotifytools_set_printf_timefmt( char * fmt ) {
timefmt = fmt;
}
/**
* Get the event queue size.
*
* This setting can also be read or modified by accessing the file
* \a /proc/sys/fs/inotify/max_queued_events.
*
* @return the maximum number of events which will be queued in the kernel.
*/
int inotifytools_get_max_queued_events() {
int ret;
if ( !read_num_from_file( QUEUE_SIZE_PATH, &ret ) ) return -1;
return ret;
}
/**
* Get the maximum number of user instances of inotify.
*
* This setting can also be read or modified by accessing the file
* \a /proc/sys/fs/inotify/max_user_instances.
*
* @return the maximum number of inotify file descriptors a single user can
* obtain.
*/
int inotifytools_get_max_user_instances() {
int ret;
if ( !read_num_from_file( INSTANCES_PATH, &ret ) ) return -1;
return ret;
}
/**
* Get the maximum number of user watches.
*
* This setting can also be read or modified by accessing the file
* \a /proc/sys/fs/inotify/max_user_watches.
*
* @return the maximum number of inotify watches a single user can obtain per
* inotify instance.
*/
int inotifytools_get_max_user_watches() {
int ret;
if ( !read_num_from_file( WATCHES_SIZE_PATH, &ret ) ) return -1;
return ret;
}
/**
* Ignore inotify events matching a particular regular expression.
*
* @a pattern is a regular expression and @a flags is a bitwise combination of
* POSIX regular expression flags. @a invert determines the type of filtering:
* 0 (--exclude[i]): exclude all files matching @a pattern
* 1 (--include[i]): exclude all files except those matching @a pattern
*
* On future calls to inotifytools_next_events() or inotifytools_next_event(),
* the regular expression is executed on the filename of files on which
* events occur. If the regular expression matches, the matched event will be
* ignored.
*/
static int do_ignore_events_by_regex( char const *pattern, int flags, int invert ) {
if (!pattern) {
if (regex) {
regfree(regex);
free(regex);
regex = 0;
}
return 1;
}
if (regex) { regfree(regex); }
else { regex = (regex_t *)malloc(sizeof(regex_t)); }
invert_regexp = invert;
int ret = regcomp(regex, pattern, flags | REG_NOSUB);
if (0 == ret) return 1;
regfree(regex);
free(regex);
regex = 0;
error = EINVAL;
return 0;
}
/**
* Ignore inotify events matching a particular regular expression.
*
* @a pattern is a regular expression and @a flags is a bitwise combination of
* POSIX regular expression flags.
*
* On future calls to inotifytools_next_events() or inotifytools_next_event(),
* the regular expression is executed on the filename of files on which
* events occur. If the regular expression matches, the matched event will be
* ignored.
*/
int inotifytools_ignore_events_by_regex( char const *pattern, int flags ) {
return do_ignore_events_by_regex(pattern, flags, 0);
}
/**
* Ignore inotify events NOT matching a particular regular expression.
*
* @a pattern is a regular expression and @a flags is a bitwise combination of
* POSIX regular expression flags.
*
* On future calls to inotifytools_next_events() or inotifytools_next_event(),
* the regular expression is executed on the filename of files on which
* events occur. If the regular expression matches, the matched event will be
* ignored.
*/
int inotifytools_ignore_events_by_inverted_regex( char const *pattern, int flags ) {
return do_ignore_events_by_regex(pattern, flags, 1);
}
int event_compare(const void *p1, const void *p2, const void *config)
{
if (!p1 || !p2) return p1 - p2;
char asc = 1;
int sort_event = (int)config;
if (sort_event == -1) {
sort_event = 0;
asc = 0;
} else if (sort_event < 0) {
sort_event = -sort_event;
asc = 0;
}
int *i1 = stat_ptr((watch*)p1, sort_event);
int *i2 = stat_ptr((watch*)p2, sort_event);
if (0 == *i1 - *i2) {
return ((watch*)p1)->wd - ((watch*)p2)->wd;
}
if (asc)
return *i1 - *i2;
else
return *i2 - *i1;
}
struct rbtree *inotifytools_wd_sorted_by_event(int sort_event)
{
struct rbtree *ret = rbinit(event_compare, (void*)sort_event);
RBLIST *all = rbopenlist(tree_wd);
void const *p = rbreadlist(all);
while (p) {
void const *r = rbsearch(p, ret);
niceassert((int)(r == p), "Couldn't insert watch into new tree");
p = rbreadlist(all);
}
rbcloselist(all);
return ret;
}
Jump to Line
Something went wrong with that request. Please try again.