inotify_watch.c File Reference

Use inotify to establish file watches. More...

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/inotify.h>
#include <signal.h>
#include "dhash.h"
#include "path_utils.h"
#include "logging.h"
#include "inotify_watch.h"
#include "util.h"
#include "lwatch.h"

Go to the source code of this file.

Classes

struct  inotify_cookie_cache_entry_t
 Struct used to track inotifiy MOVE operations (e.g. More...
struct  path_and_result_t
 Convenience struct used in callbacks to test a path and get return a boolean result. More...

Defines

#define _GNU_SOURCE
 Turn on GNU extensions.
#define INOTIFY_EVENT_SIZE   (sizeof (struct inotify_event))
 size of the inotify event structure, not counting name
#define INOTIFY_COOKIE_CACHE_SIZE   10
 How many pending rename operations we'll track.
#define INOTIFY_EVENTS
 The inotify events we need to be informed of.
#define INITIAL_HASH_TABLE_SIZE   16
 How many entries to initially reserve in global hash tables.
#define INITIAL_DESCENDANT_HASH_TABLE_SIZE   2
 How many entries to initially reserve in the path watch object descendant hash table.

Typedefs

typedef void(* inotify_lost_cookie_callback_t )(struct inotify_cookie_cache_entry_t *move)
 Definition of callback signature used when interested party wants to be informed that a rename operation never saw both the previous and new names, only either the previous or the new name was ever seen.

Functions

static bool visit_descendants_and_test_for_ancestor (hash_entry_t *item, void *user_data)
 Hash iteration callback used to determine if subject path is an ancestor of any descendant.
static bool is_path_an_ancestor_of_any_watch_descendants (struct path_watch_t *pw, const char *subject_path)
 Return true if a portion of the path could be one of the descendants being watched.
static int free_path_watch (struct path_watch_t **ppw)
 Free a watch object when no longer needed.
static int new_path_watch (const char *path, struct path_watch_t **pw_return)
 Create and intialize a new path watch object, add it's path to the watch subsystem.
static int destroy_path_watch (struct path_watch_t **ppw)
 Destroy the path watch object; remove it's path from watch subsystem, then free it's memory and resources, set the variable pointed to by ppw to NULL;.
static int destroy_path_watch_if_empty (struct path_watch_t **ppw)
 If the path watch object has no descendant targets to watch then destroy it.
static int add_path_to_target_list (const char *path)
 Add this path to list of watch targets.
static int remove_path_from_target_list (const char *path)
 Removes a path from the list of watch targets.
static int track_path_watch (struct path_watch_t *pw)
 Do the bookkeeping to track a path watch object.
static int untrack_path_watch (struct path_watch_t *pw)
 Remove a path watch object from our tracking data structures.
static void lost_rename_callback (struct inotify_cookie_cache_entry_t *move)
 A directory or file was renamed but we don't have both names, invoke this to emit a rename event.
static int process_event (struct inotify_event *event)
 Given an inotify event, process it and transform it into a generic lwatch_event.
static int relocate_watches (struct path_watch_t *pw)
 Move each descendant watch target of this directory to it's optimal watch directory.
static struct
inotify_cookie_cache_entry_t
inotify_cookie_cache_operator (struct inotify_event *event, const char *path)
 Process an inotify MOVE (i.e.
static void flush_inotify_cookie_cache (void)
 Flush any pending inotify MOVE (rename) events from the cache.
static void log_watch_path_table (const char *fmt,...)
 Write the watch path table to our log with formatted message prepended.
static void log_target_table (const char *fmt,...)
 Write the target table to our log with formatted message prepended.
int inotify_watch_init ()
 Initialize the inotify monitoring back end.
int inotify_watch_fini ()
 Close the inotify monitoring back end, stop all monitoring, free all resources.
bool is_watch_target (const char *path)
 Return true if path parameter is a watch target.
bool is_path_watched (const char *path)
 Return true if the given path is being watched by inotify.
const char * inotify_watch_error_string (int error)
 Translate error code to string description.
char * inotify_mask_string (unsigned int mask, const char *separator)
 Given an inotify event bitmask return a string of all the enabled flags.
char * path_watch_string (struct path_watch_t *pw)
 Render a path_watch_t struct as a string.
struct path_watch_tfind_watch_by_path (const char *path)
 Given a path lookup the path watch object associated with it.
struct path_watch_tfind_watch_by_watch_id (int watch_id)
 Given an inotify watch id lookup the path watch object associated with it.
int inotify_start_monitoring (const char *path_arg)
 Start monitoring the target path with inotify.
int inotify_stop_monitoring (const char *path_arg)
 Stop monitoring the target path with inotify.
int get_inotify_fd ()
 Return the file descriptor used by the inotify back end.
int read_events ()
 Loop used to read inotify events and dispatch them.
lwatch_event_callback_t register_lwatch_event_callback (lwatch_event_callback_t callback)
 Register a callback to receive log watch events.

Variables

bool keep_watching = true
 Flag used to indicate if the read_events() loop should terminate or continue.
static
inotify_lost_cookie_callback_t 
inotify_lost_cookie_callback = NULL
 User callback invoked when an inotify move has a "lost" old or new name as part of a rename (MOVE) event.
static struct
inotify_cookie_cache_entry_t 
inotify_cookie_cache [INOTIFY_COOKIE_CACHE_SIZE]
 Cache of incomplete inotify rename (MOVE) events.
static int n_cookies = 0
 Number of entries in the cache of incomplete inotify rename (MOVE) events.
static hash_table_t * watch_path_table = NULL
 Hash table which maps from a path name being watched by inotify to a path watch object.
static hash_table_t * watch_id_table = NULL
 Hash table which maps from an id provided by inotify to a path watch object.
static hash_table_t * target_table = NULL
 Hash table of watch targets.
static int inotify_fd = -1
 The file descriptor provided by inotify to read inotify events from.
static lwatch_event_callback_t lwatch_event_callback = NULL
 User callback for sending log watch events from the inotify back end to the log watcher.


Detailed Description

Use inotify to establish file watches.

Definition in file inotify_watch.c.


Define Documentation

#define INOTIFY_COOKIE_CACHE_SIZE   10

How many pending rename operations we'll track.

See How inotify rename cookies are managed..

Definition at line 172 of file inotify_watch.c.

Referenced by inotify_cookie_cache_operator().

#define INOTIFY_EVENTS

Value:

(IN_MOVE | IN_CREATE | IN_ISDIR | IN_MODIFY | IN_OPEN | \
                        IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF | \
                        IN_IGNORED | IN_MOVED_FROM | IN_MOVED_TO)
The inotify events we need to be informed of.

Definition at line 177 of file inotify_watch.c.

Referenced by new_path_watch().


Typedef Documentation

Definition of callback signature used when interested party wants to be informed that a rename operation never saw both the previous and new names, only either the previous or the new name was ever seen.

See How inotify rename cookies are managed..

Definition at line 224 of file inotify_watch.c.


Function Documentation

static int add_path_to_target_list ( const char *  path  )  [static]

Add this path to list of watch targets.

Parameters:
[in] path The path to add to the list of watch targets
Returns:
SUCCESS (0) or non-zero error code otherwise

Definition at line 504 of file inotify_watch.c.

Referenced by inotify_start_monitoring().

00505 {
00506     int error;
00507     hash_key_t key;
00508     hash_value_t value;
00509 
00510     key.type = HASH_KEY_STRING;
00511     key.str = path;
00512     value.type = HASH_VALUE_UNDEF;
00513 
00514     if ((error = hash_enter(target_table, &key, &value)) != HASH_SUCCESS) {
00515         log_msg(LOG_ERROR, _("cannot add to target table \"%s\" (%s)\n"),
00516                 path, error_string(error));
00517         return error;
00518     }
00519     return SUCCESS;
00520 }

static int destroy_path_watch ( struct path_watch_t **  ppw  )  [static]

Destroy the path watch object; remove it's path from watch subsystem, then free it's memory and resources, set the variable pointed to by ppw to NULL;.

Parameters:
[out] ppw Pointer to the path watch object to destroy, will be set to NULL on SUCCESS.
Returns:
SUCCESS (0) or non-zero error code otherwise

Definition at line 463 of file inotify_watch.c.

Referenced by destroy_path_watch_if_empty(), and inotify_watch_fini().

00464 {
00465     struct path_watch_t *pw = *ppw;
00466     int error;
00467 
00468     log_msg(LOG_DEBUG, _("destroying watch for [%d] \"%s\"\n"),  pw->watch_id, pw->path);
00469     inotify_rm_watch(inotify_fd, pw->watch_id);
00470     if ((error = untrack_path_watch(pw)) != SUCCESS) {
00471         log_msg(LOG_ERROR, _("Could not untrack_path_watch for \"%s\" (%s)\n"),
00472                 pw->path, error_string(error));
00473     }
00474     free_path_watch(ppw);
00475     return SUCCESS;
00476 }

static int destroy_path_watch_if_empty ( struct path_watch_t **  ppw  )  [static]

If the path watch object has no descendant targets to watch then destroy it.

If the path watch is destroyed the variable pointed to by ppw will be set to NULL.

Parameters:
[out] ppw Pointer to the path watch object to destroy, will be set to NULL if destroyed.
Returns:
SUCCESS (0) or non-zero error code otherwise

Definition at line 484 of file inotify_watch.c.

Referenced by inotify_stop_monitoring(), and relocate_watches().

00485 {
00486     struct path_watch_t *pw = *ppw;
00487     int error;
00488 
00489     error = SUCCESS;
00490     if (hash_count(pw->descendant_path_table) == 0) {
00491         log_msg(LOG_DEBUG, _("destroying empty watch for [%d] \"%s\"\n"), pw->watch_id, pw->path);
00492         if ((error = destroy_path_watch(ppw)) != SUCCESS) {
00493             log_msg(LOG_ERROR, _("destroy_path_watch() failed (%s)\n"), error_string(error));
00494         }
00495     }
00496     return error;
00497 }

struct path_watch_t* find_watch_by_path ( const char *  path  )  [read]

Given a path lookup the path watch object associated with it.

Parameters:
[in] path The path whose matching watch object is desired.
Returns:
Pointer to the path watch object or NULL if not found.

Definition at line 1407 of file inotify_watch.c.

Referenced by inotify_start_monitoring().

01408 {
01409     int error;
01410     hash_key_t key;
01411     hash_value_t value;
01412     struct path_watch_t *pw;
01413 
01414     key.type = HASH_KEY_STRING;
01415     key.str = path;
01416 
01417     if ((error = hash_lookup(watch_path_table, &key, &value)) != HASH_SUCCESS) {
01418         log_msg(LOG_VERBOSE_DEBUG, _("cannot find pathname \"%s\"\n"),  path);
01419         return NULL;
01420     }
01421 
01422     pw = (struct path_watch_t *) value.ptr;
01423     return pw;
01424 }

struct path_watch_t* find_watch_by_watch_id ( int  watch_id  )  [read]

Given an inotify watch id lookup the path watch object associated with it.

Parameters:
[in] watch_id The watch_id assigned by inotify to identify the watch
Returns:
Pointer to the path watch object or NULL if not found.

Definition at line 1431 of file inotify_watch.c.

Referenced by process_event().

01432 {
01433     int error;
01434     hash_key_t key;
01435     hash_value_t value;
01436     struct path_watch_t *pw;
01437 
01438     key.type = HASH_KEY_ULONG;
01439     key.ul = watch_id;
01440 
01441     if ((error = hash_lookup(watch_id_table, &key, &value)) != HASH_SUCCESS) {
01442         log_msg(LOG_DEBUG, _("cannot find watch_id %d\n"),  watch_id);
01443         return NULL;
01444     }
01445 
01446     pw = (struct path_watch_t *) value.ptr;
01447     return pw;
01448 }

static void flush_inotify_cookie_cache ( void   )  [static]

Flush any pending inotify MOVE (rename) events from the cache.

Returns:
void
Inotify rename events which do not yet have a matching entry for either the old or the new name are considered "lost" and persist in the cache waiting for their matching mate (other member of the rename pair). This routine flushes any pending rename events from the cache. By definition each flushed event will be incomplete (either the old or the new path information was lost). Flushed events are emited via the lost cookie callback. After flushing the cache will be empty.

See How inotify rename cookies are managed..

Definition at line 1095 of file inotify_watch.c.

Referenced by inotify_watch_fini().

01096 {
01097     int i;
01098     struct inotify_cookie_cache_entry_t *entry;
01099 
01100     for (i = 0; i < n_cookies; i++) {
01101         entry = &inotify_cookie_cache[i];
01102         if (inotify_lost_cookie_callback)
01103             inotify_lost_cookie_callback(entry);
01104     }
01105     n_cookies = 0;
01106 }

static int free_path_watch ( struct path_watch_t **  ppw  )  [static]

Free a watch object when no longer needed.

Parameters:
[out] ppw Pointer to the path watch object to free, will be set to NULL
Returns:
SUCCESS (0) or non-zero error code otherwise

Definition at line 390 of file inotify_watch.c.

Referenced by destroy_path_watch(), inotify_start_monitoring(), new_path_watch(), and process_event().

00391 {
00392     struct path_watch_t *pw = *ppw;
00393     int error = SUCCESS;
00394 
00395     if (pw->descendant_path_table) {
00396         if ((error = hash_destroy(pw->descendant_path_table)) != HASH_SUCCESS) {
00397             log_msg(LOG_ERROR, _("could not free file hash table error=%d\n"),  error);
00398         }
00399     }
00400 
00401     free(pw);
00402     *ppw = NULL;
00403 
00404     return error;
00405 }

int get_inotify_fd ( void   ) 

Return the file descriptor used by the inotify back end.

Returns:
Integer file descriptor used to read inotify events
The inotify file descriptor is used to read inotify events.

Definition at line 1627 of file inotify_watch.c.

Referenced by main_loop().

01628 {
01629     return inotify_fd;
01630 }

static struct inotify_cookie_cache_entry_t * inotify_cookie_cache_operator ( struct inotify_event *  event,
const char *  path 
) [static, read]

Process an inotify MOVE (i.e.

rename) event which come in pairs.

Parameters:
[in] event An inotify MOVE event to process
[in] path The path name associated with the event
Returns:
Pointer to matching event if found, NULL otherwise
Inotify MOVE (i.e. rename) events potentially come in pairs, one event for the old name and one event for the new name. The cookie field in the event matches the two events together. It is possible that either the old or the new name is not known because the directory containing it did not have a watch established on it (these are called "lost" rename events because either the old or the new name has been lost). For those instances only one event will be generated. When a MOVE event arrives we enter it in a cache where it may be paired with it's matching mate. When entering an event if it's mate is found then a struct is returned describing the pair and the pair is removed from the cache. Otherwise NULL is returned indicating we must wait for the other half of the pair.

See How inotify rename cookies are managed..

Definition at line 1017 of file inotify_watch.c.

Referenced by process_event().

01018 {
01019     int error;
01020     int i;
01021     struct inotify_cookie_cache_entry_t *entry;
01022     static struct inotify_cookie_cache_entry_t result; /* FIXME */
01023 
01024     /* see if the cookie is present, if so remove it and return True */
01025     for (i = 0; i < n_cookies; i++) {
01026         if (inotify_cookie_cache[i].cookie == event->cookie) { /* Found, delete the entry */
01027             entry = &inotify_cookie_cache[i];
01028             if (event->mask & IN_MOVED_FROM) {
01029                 entry->prev_watch_id = event->wd;
01030                 if ((error = copy_path(entry->prev_path, path, sizeof(entry->prev_path))) != SUCCESS) {
01031                     log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), path, error_string(error));
01032                 }
01033             } else if (event->mask & IN_MOVED_TO) {
01034                 entry->new_watch_id = event->wd;
01035                 if ((error = copy_path(entry->new_path, path, sizeof(entry->new_path))) != SUCCESS) {
01036                     log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), path, error_string(error));
01037                 }
01038             }
01039             result = *entry;
01040 
01041             /* fill gap of deleted entry by shifting all entries above the gap downward */
01042             for (i++; i < n_cookies; i++) {
01043                 inotify_cookie_cache[i - 1] = inotify_cookie_cache[i];
01044             }
01045             n_cookies--;
01046             return &result;
01047         }
01048     }
01049     /* Not Found */
01050     if (n_cookies == INOTIFY_COOKIE_CACHE_SIZE) {
01051          /* Cache full, remove oldest by shifting all entries down,
01052           * provide notification if requested. */
01053         if (inotify_lost_cookie_callback)
01054             inotify_lost_cookie_callback(&inotify_cookie_cache[0]);
01055         n_cookies--;
01056         for (i = 0; i < n_cookies; i++) {
01057             inotify_cookie_cache[i] = inotify_cookie_cache[i + 1];
01058         }
01059     }
01060     entry = &inotify_cookie_cache[n_cookies];
01061     entry->cookie = event->cookie;
01062     if (event->mask & IN_MOVED_FROM) {
01063         entry->prev_watch_id = event->wd;
01064         entry->new_watch_id = -1;
01065         if ((error = copy_path(entry->prev_path, path, sizeof(entry->prev_path))) != SUCCESS) {
01066             log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), path, error_string(error));
01067         }
01068         entry->new_path[0] = 0;
01069     } else if (event->mask & IN_MOVED_TO) {
01070         entry->prev_watch_id = -1;
01071         entry->new_watch_id = event->wd;
01072         entry->prev_path[0] = 0;
01073         if ((error = copy_path(entry->new_path, path, sizeof(entry->new_path))) != SUCCESS) {
01074             log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), path, error_string(error));
01075         }
01076     }
01077     n_cookies++;
01078     return NULL;
01079 }

char* inotify_mask_string ( unsigned int  mask,
const char *  separator 
)

Given an inotify event bitmask return a string of all the enabled flags.

Parameters:
[in] mask Bitmask of inotify event flags
[in] separator String used to separate individual flags
Returns:
static string with each enabled flag separated by the separator string

Definition at line 1305 of file inotify_watch.c.

Referenced by process_event().

01306 {
01307     static __thread char buf[1024]; /* thread local static buffer */
01308     char *p, *buf_end;
01309     size_t separator_len;
01310     unsigned int unknown;
01311 
01312     p = buf;                     /* current position in buffer */
01313     buf_end = &buf[sizeof(buf)]; /* non-inclusive end of buffer */
01314     separator_len = strlen(separator);
01315     *p = 0;
01316 
01317     /* validate there is nothing in the event mask we don't know about */
01318     unknown = mask & ~(IN_ACCESS | IN_MODIFY | IN_ATTRIB | IN_CLOSE_WRITE |
01319                         IN_CLOSE_NOWRITE | IN_OPEN | IN_MOVED_FROM | IN_MOVED_TO |
01320                         IN_CREATE | IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF |
01321                         IN_UNMOUNT | IN_Q_OVERFLOW | IN_IGNORED | IN_ONLYDIR |
01322                         IN_DONT_FOLLOW | IN_MASK_ADD | IN_ISDIR | IN_ONESHOT);
01323 
01324     if (unknown) p += snprintf(p, buf_end - p, "unknown bits=0x%x%s", unknown, separator);
01325     if (p >= buf_end) goto fail;
01326 
01327     if (mask & IN_ACCESS)        p += snprintf(p, buf_end - p, "ACCESS%s",        separator);
01328     if (p >= buf_end) goto fail;
01329     if (mask & IN_MODIFY)        p += snprintf(p, buf_end - p, "MODIFY%s",        separator);
01330     if (p >= buf_end) goto fail;
01331     if (mask & IN_ATTRIB)        p += snprintf(p, buf_end - p, "ATTRIB%s",        separator);
01332     if (p >= buf_end) goto fail;
01333     if (mask & IN_CLOSE_WRITE)   p += snprintf(p, buf_end - p, "CLOSE_WRITE%s",   separator);
01334     if (p >= buf_end) goto fail;
01335     if (mask & IN_CLOSE_NOWRITE) p += snprintf(p, buf_end - p, "CLOSE_NOWRITE%s", separator);
01336     if (p >= buf_end) goto fail;
01337     if (mask & IN_OPEN)          p += snprintf(p, buf_end - p, "OPEN%s",          separator);
01338     if (p >= buf_end) goto fail;
01339     if (mask & IN_MOVED_FROM)    p += snprintf(p, buf_end - p, "MOVED_FROM%s",    separator);
01340     if (p >= buf_end) goto fail;
01341     if (mask & IN_MOVED_TO)      p += snprintf(p, buf_end - p, "MOVED_TO%s",      separator);
01342     if (p >= buf_end) goto fail;
01343     if (mask & IN_CREATE)        p += snprintf(p, buf_end - p, "CREATE%s",        separator);
01344     if (p >= buf_end) goto fail;
01345     if (mask & IN_DELETE)        p += snprintf(p, buf_end - p, "DELETE%s",        separator);
01346     if (p >= buf_end) goto fail;
01347     if (mask & IN_DELETE_SELF)   p += snprintf(p, buf_end - p, "DELETE_SELF%s",   separator);
01348     if (p >= buf_end) goto fail;
01349     if (mask & IN_MOVE_SELF)     p += snprintf(p, buf_end - p, "MOVE_SELF%s",     separator);
01350     if (p >= buf_end) goto fail;
01351     if (mask & IN_UNMOUNT)       p += snprintf(p, buf_end - p, "UNMOUNT%s",       separator);
01352     if (p >= buf_end) goto fail;
01353     if (mask & IN_Q_OVERFLOW)    p += snprintf(p, buf_end - p, "Q_OVERFLOW%s",    separator);
01354     if (p >= buf_end) goto fail;
01355     if (mask & IN_IGNORED)       p += snprintf(p, buf_end - p, "IGNORED%s",       separator);
01356     if (p >= buf_end) goto fail;
01357     if (mask & IN_ONLYDIR)       p += snprintf(p, buf_end - p, "ONLYDIR%s",       separator);
01358     if (p >= buf_end) goto fail;
01359     if (mask & IN_DONT_FOLLOW)   p += snprintf(p, buf_end - p, "DONT_FOLLOW%s",   separator);
01360     if (p >= buf_end) goto fail;
01361     if (mask & IN_MASK_ADD)      p += snprintf(p, buf_end - p, "MASK_ADD%s",      separator);
01362     if (p >= buf_end) goto fail;
01363     if (mask & IN_ISDIR)         p += snprintf(p, buf_end - p, "ISDIR%s",         separator);
01364     if (p >= buf_end) goto fail;
01365     if (mask & IN_ONESHOT)       p += snprintf(p, buf_end - p, "ONESHOT%s",       separator);
01366     if (p >= buf_end) goto fail;
01367 
01368     if (p > buf) p[-separator_len] = 0;
01369 
01370     return buf;
01371  fail:
01372     /* exhausted the buffer; NULL terminate then return truncated string */
01373     buf_end[-1] = 0;   /* should already be NULL terminated, but be safe */
01374     return buf;
01375 }

int inotify_start_monitoring ( const char *  path_arg  ) 

Start monitoring the target path with inotify.

Parameters:
[in] path_arg The pathname to monitor, it will be normalized and made absolute
Returns:
SUCCESS (0) or non-zero error code otherwise

Definition at line 1455 of file inotify_watch.c.

Referenced by process_file_rename(), relocate_watches(), start_monitoring(), and validate_backups_in_database().

01456 {
01457     int error;
01458     char path[PATH_MAX];
01459     hash_key_t key;
01460     hash_value_t value;
01461     char existing_directory_ancestor[PATH_MAX];
01462     struct path_watch_t *pw;
01463 
01464     /* Normalize the path passed to us */
01465     if ((error = make_normalized_absolute_path(path, sizeof(path), path_arg)) != SUCCESS) {
01466         log_msg(LOG_ERROR, _("failed make_normalized_absolute_path from \"%s\" (%s)\n"),
01467                 path_arg, error_string(error));
01468         return error;
01469     }
01470 
01471     /* Assure we're not already watching this path */
01472     if (is_watch_target(path)) {
01473         error = INOTIFY_WATCH_ALREADY_WATCHING;
01474         log_msg(LOG_ERROR, _("path is already being watched \"%s\"\n"),  path);
01475         return error;
01476     }
01477 
01478     log_msg(LOG_INFO, _("watching target \"%s\"\n"),  path);
01479 
01480     /*
01481      * We watch the directory which contains the path, not the path itself. To
01482      * establish a watch on a directory the directory must exist in the
01483      * filesystem so we search for the nearest directory ancestor containing the
01484      * path which exists in the file system.  This is where the watch will be
01485      * established.
01486      *
01487      * If the directory containing the path does not yet exist we watch the
01488      * closest directory to the final path component and will move the watch to
01489      * a closer directory when the missing directory component is later created
01490      * in the directory which was chosen to watch.
01491      *
01492      * FIXME
01493      * WARNING: Race Condition. If the missing directory is created before the
01494      * watch is established we won't see the directory creation event which
01495      * tells us we can move the watch directory closer to the target. The only
01496      * way to protect against this is to periodically walk all the watch targets
01497      * and and adjust their watch directories.
01498      */
01499     if ((error = find_existing_directory_ancestor(existing_directory_ancestor,
01500                                                   sizeof(existing_directory_ancestor),
01501                                                   path) != SUCCESS)) {
01502         log_msg(LOG_ERROR, _("failed find_existing_directory_ancestor \"%s\" (%s)\n"),
01503                 path, error_string(error));
01504         return error;
01505     }
01506 
01507     /*
01508      * If we're already watching the selected directory get the watch object
01509      * previously created for watching that directory, otherwise create a new
01510      * watch object for the directory.
01511      */
01512     if ((pw = find_watch_by_path(existing_directory_ancestor)) == NULL) {
01513         if ((error = new_path_watch(existing_directory_ancestor, &pw)) != SUCCESS) {
01514             log_msg(LOG_ERROR, _("could not allocate new watch for \"%s\" (%s)\n"),
01515                     existing_directory_ancestor, error_string(error));
01516             return error;
01517         }
01518     }
01519 
01520     /* Do some basic bookkeeping */
01521 
01522     /* Add the path to our list of targets. */
01523     if ((error = add_path_to_target_list(path)) != SUCCESS) {
01524         log_msg(LOG_ERROR, _("could not insert path into target table \"%s\" (%s)\n"),
01525                 pw->path, error_string(error));
01526         inotify_rm_watch(inotify_fd, pw->watch_id);
01527         free_path_watch(&pw);
01528         return error;
01529     }
01530 
01531     /* Add the watch object to our list of directories we're watching. */
01532     if ((error = track_path_watch(pw)) != SUCCESS) {
01533         log_msg(LOG_ERROR, _("could not insert watch into tables for \"%s\" (%s)\n"),
01534                 pw->path, error_string(error));
01535         inotify_rm_watch(inotify_fd, pw->watch_id);
01536         free_path_watch(&pw);
01537         return error;
01538     }
01539 
01540     log_msg(LOG_DEBUG, _("adding path \"%s\" to descendant_path_table for directory \"%s\"\n"),
01541             path, pw->path);
01542 
01543     /* Add the path to the list of targets the watch object is responsible for watching (e.g. descendants). */
01544     key.type = HASH_KEY_STRING;
01545     key.str = path;
01546     value.type = HASH_VALUE_UNDEF;
01547 
01548     if ((error = hash_enter(pw->descendant_path_table, &key, &value)) != HASH_SUCCESS) {
01549         log_msg(LOG_ERROR, _("cannot add path \"%s\" to path_watch_t descendant_path_table \"%s\" (%s)\n"),
01550                 path, pw->path, error_string(error));
01551         return error;
01552     }
01553 
01554     return SUCCESS;
01555 }

int inotify_stop_monitoring ( const char *  path_arg  ) 

Stop monitoring the target path with inotify.

Parameters:
[in] path_arg The pathname cease monitoring, it will be normalized and made absolute
Returns:
SUCCESS (0) or non-zero error code otherwise

Definition at line 1562 of file inotify_watch.c.

01563 {
01564     int error;
01565     char path[PATH_MAX];
01566     unsigned long i, count;
01567     hash_key_t key;
01568     hash_entry_t *entries, *entry;
01569     struct path_watch_t *pw;
01570     int path_count;
01571 
01572     if ((error = make_normalized_absolute_path(path, sizeof(path), path_arg)) != SUCCESS) {
01573         log_msg(LOG_ERROR, _("failed make_normalized_absolute_path from \"%s\" (%s)\n"),
01574                 path_arg, error_string(error));
01575         return error;
01576     }
01577 
01578     if (!is_watch_target(path)) {
01579         error = INOTIFY_WATCH_NOT_WATCHED;
01580         log_msg(LOG_ERROR, _("path was not being watched \"%s\"\n"),  path);
01581         return error;
01582     }
01583 
01584     log_msg(LOG_INFO, _("removing watch target \"%s\"\n"),  path);
01585 
01586     key.type = HASH_KEY_STRING;
01587     key.str = path;
01588 
01589     path_count = 0;
01590     hash_entries(watch_id_table, &count, &entries);
01591     for (i = 0, entry = entries; i < count; i++, entry++) {
01592         pw = entry->value.ptr;
01593 
01594         if (hash_has_key(pw->descendant_path_table, &key)) {
01595             path_count++;
01596             if ((error = hash_delete(pw->descendant_path_table, &key)) != HASH_SUCCESS) {
01597                 log_msg(LOG_ERROR, _("cannot delete path \"%s\" from path_watch_t descendant_path_table \"%s\" (%s)\n"),
01598                         path, pw->path, error_string(error));
01599             }
01600             if ((error = destroy_path_watch_if_empty(&pw)) != SUCCESS) {
01601                 log_msg(LOG_ERROR, _("destroy_path_watch_if_empty failed (%s)\n"), error_string(error));
01602             }
01603         }
01604     }
01605     free(entries);
01606 
01607     if (path_count != 1) {
01608         log_msg(LOG_ERROR, _("found %d instances of \"%s\" but expected exactly one\n"),
01609                 path_count, path);
01610     }
01611 
01612     if ((error = remove_path_from_target_list(path)) != SUCCESS) {
01613         log_msg(LOG_ERROR, _("could not remove path from target table \"%s\" (%s)\n"),
01614                 path, error_string(error));
01615     }
01616 
01617     return error;
01618 }

const char* inotify_watch_error_string ( int  error  ) 

Translate error code to string description.

Parameters:
[in] error Error code whose string description is sought.
Returns:
string describing the error
Note: IS_INOTIFY_WATCH_ERROR(error) should true otherwise the error code is outside the set of values this function knows about. If the error code is not recognized the string "unknown(XXX)" will be returned instead where XXX is the decimal representation of the error code.

Definition at line 1287 of file inotify_watch.c.

Referenced by error_string().

01288 {
01289     switch(error) {
01290     case SUCCESS:                                return _("success");
01291     case INOTIFY_WATCH_ERROR_NULL_WATCH:         return _("watch invalid, was NULL");
01292     case INOTIFY_WATCH_ERROR_WATCH_ID_NOT_FOUND: return _("could not look up watch id");
01293     case INOTIFY_WATCH_ALREADY_WATCHING:         return _("already being watched");
01294     case INOTIFY_WATCH_NOT_WATCHED:              return _("not being watched");
01295     }
01296     return NULL;
01297 }

int inotify_watch_fini ( void   ) 

Close the inotify monitoring back end, stop all monitoring, free all resources.

Returns:
SUCCESS (0) or non-zero error code otherwise

Definition at line 1197 of file inotify_watch.c.

Referenced by lwatch_fini().

01198 {
01199     unsigned long i, count;
01200     hash_entry_t *entries, *entry;
01201     struct path_watch_t *pw;
01202 
01203     flush_inotify_cookie_cache();
01204 
01205     hash_entries(watch_id_table, &count, &entries);
01206     for (i = 0, entry = entries; i < count; i++, entry++) {
01207         pw = entry->value.ptr;
01208         log_msg(LOG_VERBOSE_DEBUG, _("destroying [%d] \"%s\"\n"),  pw->watch_id, pw->path);
01209         destroy_path_watch(&pw);
01210     }
01211     free(entries);
01212 
01213     if (hash_count(watch_path_table) != 0) {
01214         log_msg(LOG_ERROR, _("watch_path_table not empty (%d) after removing all watches\n"),
01215                 hash_count(watch_path_table));
01216     }
01217 
01218     if (hash_count(watch_id_table) != 0) {
01219         log_msg(LOG_ERROR, _("watch_id_table not empty (%d) after removing all watches\n"),
01220                 hash_count(watch_id_table));
01221     }
01222 
01223     hash_destroy(watch_path_table);
01224     hash_destroy(watch_id_table);
01225     hash_destroy(target_table);
01226     close(inotify_fd);
01227 
01228     return SUCCESS;
01229 }

int inotify_watch_init ( void   ) 

Initialize the inotify monitoring back end.

Returns:
SUCCESS (0) or non-zero error code otherwise

Definition at line 1162 of file inotify_watch.c.

Referenced by lwatch_init().

01163 {
01164     int error;
01165 
01166     inotify_lost_cookie_callback = lost_rename_callback;
01167 
01168     if ((inotify_fd = inotify_init()) < 0) {
01169         error = errno;
01170         log_msg(LOG_ERROR, _("inotify_init() failed (%s)\n"), error_string(error));
01171         return error;
01172     }
01173 
01174 
01175     if ((error = hash_create(INITIAL_HASH_TABLE_SIZE, &watch_path_table, NULL)) != HASH_SUCCESS) {
01176         log_msg(LOG_ERROR, _("inotify_watch_init: cannot create pathname hash table\n"));
01177         return error;
01178     }
01179 
01180     if ((error = hash_create(INITIAL_HASH_TABLE_SIZE, &watch_id_table, NULL)) != HASH_SUCCESS) {
01181         log_msg(LOG_ERROR, _("inotify_watch_init: cannot create watch id hash table\n"));
01182         return error;
01183     }
01184 
01185     if ((error = hash_create(INITIAL_HASH_TABLE_SIZE, &target_table, NULL)) != HASH_SUCCESS) {
01186         log_msg(LOG_ERROR, _("%s: cannot create target hash table\n"));
01187         return error;
01188     }
01189 
01190     return SUCCESS;
01191 }

static bool is_path_an_ancestor_of_any_watch_descendants ( struct path_watch_t pw,
const char *  subject_path 
) [static]

Return true if a portion of the path could be one of the descendants being watched.

Parameters:
[in] pw Path watch object
[in] subject_path The path being tested
Returns:
True if the subject path is an ancestor of any descandant watches
Iterate over the list of descendant paths this watch is responsible for. If the subject path is contained in a descendant path (e.g. the subject path an ancestor of the descendant path) then return true. If the subject_path is not an ancestor of any of the descendants being watched return false.

Example:

The watch is established on "/a/b"

The watch on "/a/b" has a descendant "/a/b/c/d" it's responsible for watching for.

The subject path being tested is "/a/b/c"

In this instance the subject "/a/b/c" is an ancestor of "/a/b/c/d" and the function would return true.

This test is typically done when a new directory is added to the directory being watched. So lets say directory "c" was created in "/a/b" forming a new path of "a/b/c". We call this function asking if "/a/b/c" belongs to any of the descendants being watched. In our example one of the descendants being watched by "/a/b" is "/a/b/c/d" (we're waiting for "/a/b/c" to be created which is why "/a/b/c/d" a descendant watch of "a/b"). The function returns true indicating that the newly formed directory "/a/b/c" is indeed part of what this directory is responsible watching for and as a consequence the descendant watch for "/a/b/c/d" will be moved from "/a/b" to one of the newly created descendants of "/a/b", i.e. it will be moved to "/a/b/c" (or further down the list of path components if we discover more path components exist at the time we move the watch.

See also:
DescendantWatchesDoc

Definition at line 373 of file inotify_watch.c.

Referenced by process_event().

00374 {
00375     struct path_and_result_t data;
00376 
00377     data.path = subject_path;
00378     data.result = false;
00379 
00380     hash_iterate(pw->descendant_path_table, visit_descendants_and_test_for_ancestor, &data);
00381     return data.result;
00382 }

bool is_path_watched ( const char *  path  ) 

Return true if the given path is being watched by inotify.

Parameters:
[in] path The path to check for inotify watching
Returns:
true if the path is watched by inotify, false otherwise
Note, this is distinct from asking if a path is target, a target is something we've been asked to monitor. In the process of monitoring we will ask inotify to watch certain directories which are not targets, but may contain targets.

See also:
is_watch_target() Also see Vocabulary Terms for an explanation of targets vs. watched objects.

Definition at line 1267 of file inotify_watch.c.

01268 {
01269     hash_key_t key;
01270 
01271     key.type = HASH_KEY_STRING;
01272     key.str = path;
01273 
01274     return hash_has_key(watch_path_table, &key);
01275 }

bool is_watch_target ( const char *  path  ) 

Return true if path parameter is a watch target.

Parameters:
[in] path The path we're testing to see if it's a watch target
Returns:
True if path parameter is a watch target
Note, This answers the question if the path is a target to monitor. In other words it's something we've specifically been asked to monitor. Our implementation will establish inotify watches as a consequence of monitoring a target and is_path_watched() is correct way to ask if inotify is watching a path as opposed to this function which reports if path is target to monitor.

See also:
is_path_watched() Also see Vocabulary Terms for an explanation of targets vs. watched objects.

Definition at line 1245 of file inotify_watch.c.

Referenced by inotify_start_monitoring(), inotify_stop_monitoring(), process_event(), and start_monitoring().

01246 {
01247     hash_key_t key;
01248 
01249     key.type = HASH_KEY_STRING;
01250     key.str = path;
01251 
01252     return hash_has_key(target_table, &key);
01253 }

static void log_target_table ( const char *  fmt,
  ... 
) [static]

Write the target table to our log with formatted message prepended.

Parameters:
[in] fmt printf style format followed by optional arguments used to identify the table.
Returns:
void

Definition at line 1137 of file inotify_watch.c.

Referenced by process_event().

01138 {
01139     char buf[1024];
01140     va_list ap;
01141     char *targets = keys_string(target_table, "    ", "\n");
01142 
01143     va_start(ap, fmt);
01144     if (fmt) {
01145         vsnprintf(buf, sizeof(buf), fmt, ap);
01146     } else {
01147         *buf = 0;
01148     }
01149 
01150     log_msg(LOG_DEBUG, _("target table: %s\n%s\n"), buf, targets);
01151     free(targets);
01152 }

static void log_watch_path_table ( const char *  fmt,
  ... 
) [static]

Write the watch path table to our log with formatted message prepended.

Parameters:
[in] fmt printf style format followed by optional arguments used to identify the table.
Returns:
void

Definition at line 1114 of file inotify_watch.c.

Referenced by process_event(), track_path_watch(), and untrack_path_watch().

01115 {
01116     char buf[1024];
01117     va_list ap;
01118     char *watch_path_table_str = watch_path_table_string(watch_path_table);
01119 
01120     va_start(ap, fmt);
01121     if (fmt) {
01122         vsnprintf(buf, sizeof(buf), fmt, ap);
01123     } else {
01124         *buf = 0;
01125     }
01126 
01127     log_msg(LOG_DEBUG, _("pathname table: %s\n%s\n"), buf, watch_path_table_str);
01128     free(watch_path_table_str);
01129 }

static void lost_rename_callback ( struct inotify_cookie_cache_entry_t move  )  [static]

A directory or file was renamed but we don't have both names, invoke this to emit a rename event.

Parameters:
[in] move an entry in inotify cookie cache which is being flushed even though incomplete
Returns:
SUCCESS (0) or non-zero error code otherwise
See How inotify rename cookies are managed. for an explanation of how inotifiy handles renames and the issues surrounding it.

Definition at line 626 of file inotify_watch.c.

Referenced by inotify_watch_init().

00627 {
00628     int error;
00629 
00630     if (lwatch_event_callback) {
00631         struct lwatch_event_t event;
00632 
00633         event.event_type = LWATCH_EVENT_RENAME;
00634         if ((error = copy_path(event.rename.prev_path, move->prev_path, sizeof(event.rename.prev_path))) != SUCCESS) {
00635             log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), move->prev_path, error_string(error));
00636         }
00637         if ((error = copy_path(event.rename.new_path,  move->new_path,  sizeof(event.rename.new_path))) != SUCCESS) {
00638             log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), move->new_path, error_string(error));
00639         }
00640         lwatch_event_callback(&event);
00641     }
00642 }

int new_path_watch ( const char *  path,
struct path_watch_t **  pw_return 
) [static]

Create and intialize a new path watch object, add it's path to the watch subsystem.

Parameters:
[in] path The path being watched by this watch object
[out] pw_return The newly created path watch object is assigned to this variable
Returns:
SUCCESS (0) or non-zero error code otherwise

Definition at line 414 of file inotify_watch.c.

Referenced by inotify_start_monitoring().

00415 {
00416     struct path_watch_t *pw;
00417     int error;
00418 
00419     *pw_return = NULL;
00420 
00421     if ((pw = (struct path_watch_t *)malloc(sizeof(struct path_watch_t))) == NULL) {
00422         error = ENOMEM;
00423         log_msg(LOG_ERROR, _("\"%s\" (%s)\n"),  path, error_string(error));
00424         return error;
00425     }
00426 
00427     if ((error = copy_path(pw->path, path, sizeof(pw->path))) != SUCCESS) {
00428         log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), path, error_string(error));
00429         free_path_watch(&pw);
00430         return error;
00431     }
00432 
00433     pw->watch_id = -1;
00434     pw->descendant_path_table = NULL;
00435 
00436     if ((error = hash_create(INITIAL_DESCENDANT_HASH_TABLE_SIZE,
00437                              &pw->descendant_path_table, NULL)) != HASH_SUCCESS) {
00438         log_msg(LOG_ERROR, _("cannot create file hash table in path_watch_t object for path \"%s\"\n"), path);
00439         free_path_watch(&pw);
00440         return error;
00441     }
00442 
00443     log_msg(LOG_DEBUG, _("adding directory watch for \"%s\"\n"), path);
00444     if ((pw->watch_id = inotify_add_watch(inotify_fd, path, INOTIFY_EVENTS)) < 0) {
00445         error = errno;
00446         log_msg(LOG_ERROR, _("could not add inotify watch for \"%s\" (%s)\n"),
00447                 path, error_string(error));
00448         free_path_watch(&pw);
00449         return error;
00450     }
00451     log_msg(LOG_DEBUG, _("newly watching [%d] \"%s\"\n"), pw->watch_id, path);
00452 
00453     *pw_return = pw;
00454     return SUCCESS;
00455 }

char* path_watch_string ( struct path_watch_t pw  ) 

Render a path_watch_t struct as a string.

Parameters:
[in] pw The path watch object to render as a string
Returns:
malloc'ed string, caller must free

Definition at line 1382 of file inotify_watch.c.

Referenced by process_event(), and watch_path_table_string().

01383 {
01384     char *files;
01385     char *fw_string;
01386     int len;
01387 
01388     if (!pw) return strdup("(null)");
01389 
01390     files = keys_string(pw->descendant_path_table, "        ", "\n");
01391 
01392     len = asprintf(&fw_string, "[%d] \"%s\" n_files=%ld%s%s",
01393                    pw->watch_id, pw->path,
01394                    hash_count(pw->descendant_path_table),
01395                    hash_count(pw->descendant_path_table) ? "\n" : "", files);
01396 
01397     free(files);
01398     return fw_string;
01399 }

static int process_event ( struct inotify_event *  event  )  [static]

Given an inotify event, process it and transform it into a generic lwatch_event.

Parameters:
[in] event The inotify event to process.
Returns:
SUCCESS (0) or non-zero error code otherwise
We read inotify_events one at a time from a file descriptor the kernel provides us. Each inotify_event contains an integer watch descriptor (wd) which identifies the specific watch on a filesystem entry, a bitmask describing the contents of the event, a cookie to synchronize events, and the length of the event which indicates if an optional name is part of the event.

To isolate the log watcher code from specific monitoring backends a generic monitoring event (lwatch_event_t) is defined. Each monitoring backend is responsible for translating to the generic lwatch_event_t event and emiting an event of that type.

This routine takes an inotify_event, processes it, and then emits a generic lwatch_event_t event which the log watcher code then processes.

See Independent backends for watching. for an explantion of how inotify serves as a backend for the log watching code.

Definition at line 667 of file inotify_watch.c.

Referenced by read_events().

00668 {
00669     int error;                  /* FIXME: need to check error on all calls */
00670     struct path_watch_t *pw;
00671     char event_path[PATH_MAX];
00672 
00673     /* Lookup the watch info associated with this event in our hash table */
00674     if (!(pw = find_watch_by_watch_id(event->wd))) {
00675         log_msg(LOG_ERROR, _("could not look up watch id %d\n"),  event->wd);
00676         return INOTIFY_WATCH_ERROR_WATCH_ID_NOT_FOUND;
00677     }
00678 
00679     /* If the event has a name associated with it, form it's complete path */
00680     if (event->len) {
00681         if ((error = path_concat(event_path, sizeof(event_path), pw->path, event->name)) != SUCCESS) {
00682             log_msg(LOG_ERROR, _("failed to concatenate path \"%s\" + \"%s\" (%s)\n"),
00683                     pw->path, event->name, error_string(error));
00684             return error;
00685         }
00686     } else {
00687         if ((error = copy_path(event_path, pw->path, sizeof(event_path))) != SUCCESS) {
00688             log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), pw->path, error_string(error));
00689             return error;
00690         }
00691     }
00692 
00693     log_msg(LOG_DEBUG, _("wd=%d mask=[%s] cookie=%u len=%u name=\"%s\" path=\"%s\"\n"),
00694             event->wd, inotify_mask_string(event->mask, ","),
00695             event->cookie, event->len, event->len ? event->name : NULL,
00696             event->len ? event_path : pw->path);
00697 
00698     /*
00699      * Extra debug information. Print the watch info associated with this event
00700      * and the full contents of the watch table, i.e. the watch info for all existing
00701      * watches. Print the target table.
00702      * Generating these strings is expensive, only do if debugging.
00703      */
00704     if (debug) {
00705         char *fw_string = path_watch_string(pw);
00706 
00707         log_msg(LOG_DEBUG, _("[%s] pw=%s\n"),
00708                 inotify_mask_string(event->mask, ","), fw_string);
00709 
00710         log_watch_path_table("On entry to: %s", __FUNCTION__);
00711         log_target_table("On entry to: %s", __FUNCTION__);
00712         free(fw_string);
00713     }
00714 
00715     /*
00716      * A file or directory in this watched directory was renamed.
00717      */
00718     if (event->mask & IN_MOVE) {
00719         struct inotify_cookie_cache_entry_t *move;
00720 
00721         if ((move = inotify_cookie_cache_operator(event, event_path))) {
00722             log_msg(LOG_DEBUG, _("renamed \"%s\" --> \"%s\"\n"),
00723                     move->prev_path, move->new_path);
00724 
00725             if (is_watch_target(move->prev_path) || is_watch_target(move->new_path)) {
00726                 if (lwatch_event_callback) {
00727                     struct lwatch_event_t lw_event;
00728 
00729                     lw_event.event_type = LWATCH_EVENT_RENAME;
00730                     if ((error = copy_path(lw_event.rename.prev_path, move->prev_path,
00731                                            sizeof(lw_event.rename.prev_path))) != SUCCESS) {
00732                         log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"),
00733                                 move->prev_path, error_string(error));
00734                         return error;
00735                     }
00736                     if ((error = copy_path(lw_event.rename.new_path,  move->new_path,
00737                                            sizeof(lw_event.rename.new_path))) != SUCCESS) {
00738                         log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"),
00739                                 move->new_path, error_string(error));
00740                         return error;
00741                     }
00742                     lwatch_event_callback(&lw_event);
00743                 }
00744             }
00745         }
00746     /*
00747      * A file or directory was created in this watched directory.
00748      */
00749     } else if (event->mask & IN_CREATE) {
00750         char *new_path = event_path;
00751 
00752         if (is_path_an_ancestor_of_any_watch_descendants(pw, new_path)) {
00753             log_msg(LOG_DEBUG, _("new path %s \"%s\" is a watch target\n"),
00754                     event->mask & IN_ISDIR ? "directory":"file", new_path);
00755 
00756             if ((error = relocate_watches(pw)) != SUCCESS) {
00757                 log_msg(LOG_ERROR, _("could not relocate_watches for \"%s\" (%s)\n"),
00758                         new_path, error_string(error));
00759             }
00760 
00761             if (lwatch_event_callback) {
00762                 struct lwatch_event_t lw_event;
00763 
00764                 lw_event.event_type = LWATCH_EVENT_CREATE;
00765                 if ((error = copy_path(lw_event.path, new_path, sizeof(lw_event.path))) != SUCCESS) {
00766                     log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), new_path, error_string(error));
00767                     return error;
00768                 }
00769                 lwatch_event_callback(&lw_event);
00770             }
00771         } else {
00772             log_msg(LOG_DEBUG, _("new path %s \"%s\" is not within a watch target\n"),
00773                     event->mask & IN_ISDIR ? "directory":"file", new_path);
00774         }
00775     /*
00776      * A file or directory in this watched directory was modifed.
00777      */
00778     } else if (event->mask & IN_MODIFY) {
00779         char *modified_path = event_path;
00780 
00781         log_msg(LOG_DEBUG, _("modified \"%s\"\n"),  modified_path);
00782 
00783         if (is_watch_target(modified_path)) {
00784             log_msg(LOG_DEBUG, _("watched file is modified \"%s\"\n"),  modified_path);
00785             if (lwatch_event_callback) {
00786                 struct lwatch_event_t lw_event;
00787 
00788                 lw_event.event_type = LWATCH_EVENT_MODIFY;
00789                 if ((error = copy_path(lw_event.path, modified_path, sizeof(lw_event.path))) != SUCCESS) {
00790                     log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), modified_path, error_string(error));
00791                     return error;
00792                 }
00793                 lwatch_event_callback(&lw_event);
00794             }
00795         }
00796     /*
00797      * A file or directory in this watched directory was opened.
00798      */
00799     } else if (event->mask & IN_OPEN) {
00800         char *opened_path = event_path;
00801 
00802         if (is_watch_target(opened_path)) {
00803             log_msg(LOG_DEBUG, _("opened watched \"%s\"\n"),  opened_path);
00804             if (lwatch_event_callback) {
00805                 struct lwatch_event_t lw_event;
00806 
00807                 lw_event.event_type = LWATCH_EVENT_OPEN;
00808                 if ((error = copy_path(lw_event.path, opened_path, sizeof(lw_event.path))) != SUCCESS) {
00809                     log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), opened_path, error_string(error));
00810                     return error;
00811                 }
00812                 lwatch_event_callback(&lw_event);
00813             }
00814         } else {
00815             log_msg(LOG_DEBUG, _("opened unwatched \"%s\"\n"),  opened_path);
00816         }
00817     /*
00818      * A file or directory in this watched directory was closed.
00819      */
00820     } else if (event->mask & IN_CLOSE_WRITE) {
00821         char *closed_path = event_path;
00822 
00823         if (is_watch_target(closed_path)) {
00824             log_msg(LOG_DEBUG, _("closed watched \"%s\"\n"),  closed_path);
00825             if (lwatch_event_callback) {
00826                 struct lwatch_event_t lw_event;
00827 
00828                 lw_event.event_type = LWATCH_EVENT_CLOSE;
00829                 if ((error = copy_path(lw_event.path, closed_path, sizeof(lw_event.path))) != SUCCESS) {
00830                     log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), closed_path, error_string(error));
00831                     return error;
00832                 }
00833                 lwatch_event_callback(&lw_event);
00834             }
00835         } else {
00836             log_msg(LOG_DEBUG, _("closed unwatched \"%s\"\n"),  closed_path);
00837         }
00838     /*
00839      * A file or directory in this watched directory was deleted.
00840      */
00841     } else if (event->mask & IN_DELETE) {
00842         char *deleted_path = event_path;
00843 
00844         if (is_watch_target(deleted_path)) {
00845             log_msg(LOG_DEBUG, _("deleted watched \"%s\"\n"),  deleted_path);
00846             if (lwatch_event_callback) {
00847                 struct lwatch_event_t lw_event;
00848 
00849                 lw_event.event_type = LWATCH_EVENT_DELETE;
00850                 if ((error = copy_path(lw_event.path, deleted_path, sizeof(lw_event.path))) != SUCCESS) {
00851                     log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), deleted_path, error_string(error));
00852                     return error;
00853                 }
00854                 lwatch_event_callback(&lw_event);
00855             }
00856         } else {
00857             log_msg(LOG_DEBUG, _("deleted unwatched \"%s\"\n"),  deleted_path);
00858         }
00859 
00860     /*
00861      * This file or directory on which a watch was established was deleted.
00862      */
00863     } else if (event->mask & IN_DELETE_SELF) {
00864         char deleted_path[PATH_MAX];
00865 
00866         if ((error = copy_path(deleted_path, pw->path, sizeof(deleted_path))) != SUCCESS) {
00867             log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), pw->path, error_string(error));
00868             return error;
00869         }
00870         if ((error = relocate_watches(pw)) != SUCCESS) {
00871             log_msg(LOG_ERROR, _("IN_DELETE_SELF could not relocate_watches for \"%s\" (%s)\n"),
00872                     deleted_path, error_string(error));
00873         }
00874     /*
00875      * This watch has been removed.
00876      */
00877     } else if (event->mask & IN_IGNORED) {
00878         if ((error = untrack_path_watch(pw)) != SUCCESS) {
00879             log_msg(LOG_ERROR, _("IN_IGNORED could not untrack_path_watch for \"%s\" (%s)\n"),
00880                     pw->path, error_string(error));
00881         }
00882         if ((error = free_path_watch(&pw)) != SUCCESS) {
00883             log_msg(LOG_ERROR, _("IN_IGNORED could not free_path_watch (%s)\n"), error_string(error));
00884         }
00885 
00886     }
00887     return SUCCESS;
00888 }

int read_events ( void   ) 

Loop used to read inotify events and dispatch them.

Returns:
SUCCESS (0) or non-zero error code otherwise

Definition at line 1636 of file inotify_watch.c.

Referenced by main_loop().

01637 {
01638     char buf[1024 * (INOTIFY_EVENT_SIZE + 16)]; /* reasonable guess as to size of 1024 events */
01639     int len, error, i;
01640     bool done;
01641 
01642     /*
01643      * The behavior when the buffer given to read(2) is too small to return information
01644      * about the next event depends on the kernel version: in kernels before 2.6.21,
01645      * read(2) returns 0; since kernel 2.6.21, read(2) fails with the error EINVAL.
01646      */
01647 
01648     done = false;
01649     while (!done) {
01650         len = read (inotify_fd, buf, sizeof(buf));
01651         i = 0;
01652         if (len <= 0) {
01653             error = errno;
01654             if (error == EINTR || error == EAGAIN) { /* need to reissue system call */
01655                 continue;
01656             }
01657             else if (error == EINVAL || len == 0) {
01658                 log_msg(LOG_ERROR, _("failed, buffer too small (%s)\n"),
01659                         error_string(error));
01660                 return error;
01661             } else {
01662                 log_msg(LOG_ERROR, _("failed (%s)\n"),
01663                         error_string(error));
01664                 return error;
01665             }
01666         }
01667         while (i < len) {
01668             struct inotify_event *event;
01669 
01670             event = (struct inotify_event *) &buf[i];
01671 
01672             process_event(event);
01673             i += INOTIFY_EVENT_SIZE + event->len;
01674         }
01675         done = true;
01676     }
01677     return SUCCESS;
01678 }

lwatch_event_callback_t register_lwatch_event_callback ( lwatch_event_callback_t  callback  ) 

Register a callback to receive log watch events.

Parameters:
[in] callback The callback which will be invoked to send a log watch event.
Returns:
The previous callback pointer.

Definition at line 1685 of file inotify_watch.c.

Referenced by lwatch_init().

01686 {
01687     lwatch_event_callback_t prev_callback = lwatch_event_callback;
01688     lwatch_event_callback = callback;
01689     return prev_callback;
01690 }

static int relocate_watches ( struct path_watch_t pw  )  [static]

Move each descendant watch target of this directory to it's optimal watch directory.

Parameters:
[in] pw The path watch object to track in our internal data structures
Returns:
SUCCESS (0) or non-zero error code otherwise
Watch targets are assigned to directories (e.g. a path watch on a directory). The optimal directory is the directory containing the watch target, however that directory may not exist thus a watch cannot be established. The most optimal directory to establish the watch on is the closest existing ancestor directory for the watch target. When a watch is established on the closest existing ancestor directory that directory can watch for the creation of a subdirectory leading to the watch target. When that occurs we move the ownership of the watch to it's closest watch point. This is the purpose of this function. When a directory containing a set of watches is removed the analogous process must take place, rather than moving the watch owner further down the target path as is done with subdirectory creation the the watch owner must be moved up the target path.

A path watch object on a directory owns the responsibility for watching for a set of watch targets. It's targets may be in it's own directory or they may be further down a path hierarchy branching from this directory (in other words the basename component of the path watch appears somewhere in the chain of path components leading to the target). One consequence of this restriction is that every watch target belonging to a path watch object is a descendant of this directory, hence the name "descendent watch".

WARNING: Race Condition. There is an inherent race condition. We receive notifications of modifications to the filesystem asynchronously which triggers the processing performed here. We must assume the filesystem is changing underneath us all the time. To minimize effects of this we defer as long as possible the determination of existing directories and the computation of the optimal watch directory for a given watch target. That responsibility is given to the inotify_start_monitoring() function.

Definition at line 924 of file inotify_watch.c.

Referenced by process_event().

00925 {
00926     int error;
00927     hash_key_t *descendant, *descendant_keys;
00928     unsigned long i, count;
00929     char existing_directory_ancestor[PATH_MAX];
00930     char descendant_path[PATH_MAX];
00931 
00932     log_msg(LOG_DEBUG, _("relocate_watches for \"%s\"\n"), pw->path);
00933 
00934     /*
00935      * Because we may modify the descendant table as we iterate over all the
00936      * descendants we must use a temporary copy of the list of descendants.
00937      */
00938     if ((error = hash_keys(pw->descendant_path_table, &count, &descendant_keys)) != HASH_SUCCESS) {
00939         log_msg(LOG_ERROR, _("could not get descendant_path_table key list in \"%s\" (%s)\n"),
00940                 pw->path, error_string(error));
00941         return error;
00942     }
00943 
00944     /* Iterate over all descendant watches of this path watch object */
00945     for (i = 0, descendant = descendant_keys; i < count; i++, descendant++) {
00946         if ((error = copy_path(descendant_path, descendant->str, sizeof(descendant_path))) != SUCCESS) {
00947             log_msg(LOG_ERROR, _("failed to copy path \"%s\" (%s)\n"), descendant->str, error_string(error));
00948             continue;
00949         }
00950         /* Find what is currently the closest directory avaliable for watching the descendant */
00951         if ((error = find_existing_directory_ancestor(existing_directory_ancestor,
00952                                                       sizeof(existing_directory_ancestor),
00953                                                       descendant_path) != SUCCESS)) {
00954             log_msg(LOG_ERROR, _("failed find_existing_directory_ancestor \"%s\" (%s)\n"),
00955                     descendant_path, error_string(error));
00956             continue;
00957         }
00958         /*
00959          * If the closest existing directory is different than this directory
00960          * then move the watch from here to a closer directory by deleting it
00961          * from our table and re-creating the watch.
00962          */
00963         if (strcmp(descendant_path, existing_directory_ancestor) != 0) {
00964             log_msg(LOG_VERBOSE_DEBUG, _("existing watch \"%s\" not equal to new watch \"%s\"\n"),
00965                     descendant_path, existing_directory_ancestor);
00966 
00967             if ((error = hash_delete(pw->descendant_path_table, descendant)) != HASH_SUCCESS) {
00968                 log_msg(LOG_ERROR, _("cannot delete from descendant_path_table \"%s\" (%s)\n"),
00969                         descendant_path, error_string(error));
00970                 continue;
00971             }
00972 
00973             /* Set the watch on the optimal watch directory for this target */
00974             if ((error = inotify_start_monitoring(descendant_path)) != SUCCESS) {
00975                 log_msg(LOG_ERROR, _("cannot inotify_start_monitoring for \"%s\" (%s)\n"),
00976                         descendant_path, error_string(error));
00977                 continue;
00978             }
00979         }
00980     }
00981     free(descendant_keys);      /* clean up our temporary copy of the descendant list */
00982 
00983     /*
00984      * After processing all descendant watches it's possible they have all been
00985      * moved elsewhere leaving this directory without any targets to watch for,
00986      * if so delete ourself.
00987      */
00988     if ((error = destroy_path_watch_if_empty(&pw)) != SUCCESS) {
00989         log_msg(LOG_ERROR, _("destroy_path_watch_if_empty failed (%s)\n"),
00990                 error_string(error));
00991     }
00992 
00993     return SUCCESS;
00994 }

static int remove_path_from_target_list ( const char *  path  )  [static]

Removes a path from the list of watch targets.

Parameters:
[in] path The path to remove from the list of watch targets
Returns:
SUCCESS (0) or non-zero error code otherwise

Definition at line 527 of file inotify_watch.c.

Referenced by inotify_stop_monitoring().

00528 {
00529     int error;
00530     hash_key_t key;
00531 
00532     key.type = HASH_KEY_STRING;
00533     key.str = path;
00534 
00535     if ((error = hash_delete(target_table, &key)) != HASH_SUCCESS) {
00536         log_msg(LOG_ERROR, _("cannot remove from target table \"%s\" (%s)\n"),
00537                 path, error_string(error));
00538         return error;
00539     }
00540     return SUCCESS;
00541 }

static int track_path_watch ( struct path_watch_t pw  )  [static]

Do the bookkeeping to track a path watch object.

Parameters:
[in] pw The path watch object to track in our internal data structures
Returns:
SUCCESS (0) or non-zero error code otherwise

Definition at line 548 of file inotify_watch.c.

Referenced by inotify_start_monitoring().

00549 {
00550     int error = SUCCESS;
00551     hash_key_t key;
00552     hash_value_t value;
00553 
00554     key.type = HASH_KEY_STRING;
00555     key.str = pw->path;
00556     value.type = HASH_VALUE_PTR;
00557     value.ptr = pw;
00558 
00559     if ((error = hash_enter(watch_path_table, &key, &value)) != HASH_SUCCESS) {
00560         log_msg(LOG_ERROR, _("cannot add to pathname table \"%s\" (%s)\n"),
00561                 pw->path, error_string(error));
00562         return error;
00563     }
00564 
00565     key.type = HASH_KEY_ULONG;
00566     key.ul = pw->watch_id;
00567 
00568     if ((error = hash_enter(watch_id_table, &key, &value)) != HASH_SUCCESS) {
00569         log_msg(LOG_ERROR, _("cannot add to watch id table %d (%s)\n"),
00570                 pw->watch_id, error_string(error));
00571         return error;
00572     }
00573 
00574     if (debug > 1) log_watch_path_table("after inserting \"%s\"",  pw->path);
00575     return SUCCESS;
00576 }

static int untrack_path_watch ( struct path_watch_t pw  )  [static]

Remove a path watch object from our tracking data structures.

Parameters:
[in] pw The path watch object to remove from our internal data structures
Returns:
SUCCESS (0) or non-zero error code otherwise

Definition at line 583 of file inotify_watch.c.

Referenced by destroy_path_watch(), and process_event().

00584 {
00585     int error = SUCCESS;
00586     hash_key_t key;
00587 
00588     if (!pw) {
00589         error = INOTIFY_WATCH_ERROR_NULL_WATCH;
00590         log_msg(LOG_ERROR, _("NULL path_watch (%s)\n"),  error_string(error));
00591         return error;
00592     }
00593 
00594     key.type = HASH_KEY_STRING;
00595     key.str = pw->path;
00596 
00597     if ((error = hash_delete(watch_path_table, &key)) != HASH_SUCCESS) {
00598         log_msg(LOG_ERROR, _("cannot delete from pathname table \"%s\" (%s)\n"),
00599                 pw->path, error_string(error));
00600         return error;
00601     }
00602 
00603     key.type = HASH_KEY_ULONG;
00604     key.ul = pw->watch_id;
00605 
00606     if ((error = hash_delete(watch_id_table, &key) != HASH_SUCCESS)) {
00607         log_msg(LOG_ERROR, _("cannot delete from watch id table \"%s\" (%d)\n"),
00608                 pw->watch_id, error_string(error));
00609         return error;
00610     }
00611 
00612     if (debug > 1) log_watch_path_table("after deleting \"%s\"", pw->path);
00613     return SUCCESS;
00614 }

static bool visit_descendants_and_test_for_ancestor ( hash_entry_t *  item,
void *  user_data 
) [static]

Hash iteration callback used to determine if subject path is an ancestor of any descendant.

Parameters:
[in] item A hash table entry, the key is one of the descendant paths
[in] user_data Contains the subject path, and the return result of the test.
Returns:
Returns false to terminate iteration as soon as result is known.
How Watch Points Watch Descendants

Definition at line 324 of file inotify_watch.c.

Referenced by is_path_an_ancestor_of_any_watch_descendants().

00325 {
00326     struct path_and_result_t *data = (struct path_and_result_t *) user_data;
00327     const char *descendant_path = item->key.str;
00328     const char *subject_path = data->path;
00329 
00330     data->result = is_ancestor_path(subject_path, descendant_path);
00331     return !data->result;       /* returning false terminates the iteration */
00332 }


Variable Documentation

struct inotify_cookie_cache_entry_t inotify_cookie_cache[INOTIFY_COOKIE_CACHE_SIZE] [static]

Cache of incomplete inotify rename (MOVE) events.

See How inotify rename cookies are managed. for more explanation.

Definition at line 273 of file inotify_watch.c.

Referenced by flush_inotify_cookie_cache(), and inotify_cookie_cache_operator().

User callback invoked when an inotify move has a "lost" old or new name as part of a rename (MOVE) event.

See How inotify rename cookies are managed. for more explanation.

Definition at line 268 of file inotify_watch.c.

Referenced by flush_inotify_cookie_cache(), inotify_cookie_cache_operator(), and inotify_watch_init().

lwatch_event_callback_t lwatch_event_callback = NULL [static]

User callback for sending log watch events from the inotify back end to the log watcher.

See Independent backends for watching. for an explantion of how inotify serves as a backend for the log watching code.

Definition at line 305 of file inotify_watch.c.

Referenced by lost_rename_callback(), lwatch_init(), process_event(), and register_lwatch_event_callback().

int n_cookies = 0 [static]

Number of entries in the cache of incomplete inotify rename (MOVE) events.

See How inotify rename cookies are managed. for more explanation.

Definition at line 278 of file inotify_watch.c.

Referenced by flush_inotify_cookie_cache(), and inotify_cookie_cache_operator().

hash_table_t* target_table = NULL [static]

hash_table_t* watch_id_table = NULL [static]

Hash table which maps from an id provided by inotify to a path watch object.

See Vocabulary Terms

Definition at line 289 of file inotify_watch.c.

Referenced by find_watch_by_watch_id(), inotify_stop_monitoring(), inotify_watch_fini(), inotify_watch_init(), track_path_watch(), and untrack_path_watch().

hash_table_t* watch_path_table = NULL [static]

Hash table which maps from a path name being watched by inotify to a path watch object.

See Vocabulary Terms

Definition at line 284 of file inotify_watch.c.

Referenced by find_watch_by_path(), inotify_watch_fini(), inotify_watch_init(), is_path_watched(), log_watch_path_table(), track_path_watch(), and untrack_path_watch().


Generated on Mon Aug 31 10:06:21 2009 by  doxygen 1.5.8