Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 93 additions & 66 deletions cf-reactor/file_watcher.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,114 +30,141 @@
#include <sys/stat.h>
#include <errno.h>
Comment thread
victormlg marked this conversation as resolved.

// =========== code for EVENT_FILE_DELETED event type ===========

/* Minimum time between two logs of a persisting lstat() failure, since the
* file is checked on every poll */
#define LSTAT_ERROR_LOG_INTERVAL_SECS 60
/* Minimum time between two logs of a persisting failure to check the file,
* since the file is checked on every poll */
#define CHECK_ERROR_LOG_INTERVAL_SECS 60

typedef struct
{
char *path;
time_t last_seen; /* Time the file was last observed present, 0 if absent */
time_t lstat_error_logged; /* Time an lstat() failure was last logged, 0 if lstat() works */
time_t last_checked; /* Time of the last successful check, 0 if none yet */
time_t last_error_logged; /* Time the last error was logged, 0 if no errors */

/* What the last successful check observed */
bool last_exists;
} FileWatcherState;

/* Sets *deleted to whether the path of the watcher exists. Only
* ENOENT/ENOTDIR mean the path genuinely doesn't exist. Any other lstat()
* failure (e.g. EACCES, ESTALE) is a transient/permission error, in which case
* we can't tell whether the file was deleted, so false is returned and
* *deleted is left untouched. A persisting failure is logged at most once
* every LSTAT_ERROR_LOG_INTERVAL_SECS.
*
* Doesn't follow symlinks, i.e. a dangling symlink is not considered deleted */
static bool FileDeleted(FileWatcherState *fws, bool *deleted)

void *FileWatcherStateNew(const Rlist *args)
{
assert(fws != NULL);
assert(deleted != NULL);
assert(args != NULL);
const char *path = RlistScalarValue(args);

struct stat sb;
if (lstat(fws->path, &sb) == 0)
{
*deleted = false;
fws->lstat_error_logged = 0;
return true;
}
FileWatcherState *fws = (FileWatcherState *) xcalloc(1, sizeof(FileWatcherState));
fws->path = xstrdup(path);

if (errno == ENOENT || errno == ENOTDIR)
return fws;
}

void FileWatcherStateDestroy(void *state)
{
FileWatcherState *fws = state;
if (fws != NULL)
{
*deleted = true;
fws->lstat_error_logged = 0;
return true;
free(fws->path);
free(fws);
}
}

// =========== code shared by the file event types ===========

/* Logs the failure of operation op (with errno err) on the file of the
* watcher. A persisting failure is logged at most once every
* CHECK_ERROR_LOG_INTERVAL_SECS. */
static void LogCheckError(FileWatcherState *fws, const char *op, int err)
{
assert(fws != NULL);

const int lstat_errno = errno;
const time_t now = time(NULL);
if (fws->lstat_error_logged == 0 ||
now - fws->lstat_error_logged >= LSTAT_ERROR_LOG_INTERVAL_SECS)
if (fws->last_error_logged == 0 ||
now - fws->last_error_logged >= CHECK_ERROR_LOG_INTERVAL_SECS)
{
Log(LOG_LEVEL_ERR, "Unable to lstat '%s' while checking for file deletion: %s",
fws->path, GetErrorStrFromCode(lstat_errno));
fws->lstat_error_logged = now;
Log(LOG_LEVEL_ERR, "Unable to %s '%s' while checking for file events: %s",
op, fws->path, GetErrorStrFromCode(err));
fws->last_error_logged = now;
}
return false;
}

void *FileWatcherStateNew(const char *path)
/* Checks whether the file of the watcher exists, with lstat(). On success,
* returns true and sets *exists, as well as *sb if the file exists and sb is
* not NULL. Only ENOENT/ENOTDIR mean the file doesn't exist. On any other
* lstat() failure (e.g. EACCES, ESTALE), whether the file exists is unknown,
* so the failure is logged (see LogCheckError()) and false is returned.
*
* Doesn't follow symlinks, e.g. a dangling symlink exists */
static bool FileLstat(FileWatcherState *fws, bool *exists, struct stat *sb)
{
FileWatcherState *fws = (FileWatcherState *) xmalloc(sizeof(FileWatcherState));
assert(fws != NULL);
assert(exists != NULL);

fws->path = xstrdup(path);
fws->lstat_error_logged = 0;
struct stat buf;
if (lstat(fws->path, (sb != NULL) ? sb : &buf) == 0)
{
*exists = true;
fws->last_error_logged = 0;
return true;
}

/* If the file's presence can't be determined, treat it as not yet seen */
bool deleted;
fws->last_seen = (FileDeleted(fws, &deleted) && !deleted) ? time(NULL) : 0;
if (errno == ENOENT || errno == ENOTDIR)
{
*exists = false;
fws->last_error_logged = 0;
return true;
}

return (void *) fws;
LogCheckError(fws, "lstat", errno);
return false;
}

WatcherCheckResult FileWatcherCheckFileDeleted(void *state)
/* Records a successful check, at which the file existed or not and the event
* (e.g. "deletion") happened or not, and returns its result. The first
* successful check is only the reference for the next ones, so it can't
* fire. */
static WatcherCheckResult FileWatcherRecordObservation(FileWatcherState *fws, bool exists,
bool happened, const char *event)
{
assert(state != NULL);
FileWatcherState *fws = state;

bool deleted_now;
if (!FileDeleted(fws, &deleted_now))
{
/* Unknown state, keep last_seen as is */
return WATCHER_CHECK_ERROR;
}
assert(fws != NULL);
assert(event != NULL);

const time_t now = time(NULL);
const bool deleted = (fws->last_seen != 0) && deleted_now;
happened = happened && (fws->last_checked != 0);

if (deleted)
if (happened)
{
char last_seen_str[26];
const char *last_seen_ts = cf_strtimestamp_local(fws->last_seen, last_seen_str);
char last_checked_str[26];
const char *last_checked_ts = cf_strtimestamp_local(fws->last_checked, last_checked_str);

char now_str[26];
const char *now_ts = cf_strtimestamp_local(now, now_str);

Log(LOG_LEVEL_VERBOSE,
"Observed deletion of file '%s' at %s (last seen present at %s)",
"Observed %s of file '%s' at %s (last checked at %s)",
event,
fws->path,
(now_ts != NULL) ? now_ts : "unknown time",
(last_seen_ts != NULL) ? last_seen_ts : "unknown time");
(last_checked_ts != NULL) ? last_checked_ts : "unknown time");
}

fws->last_seen = deleted_now ? 0 : now;
return deleted ? WATCHER_CHECK_EVENT : WATCHER_CHECK_NO_EVENT;
fws->last_checked = now;
fws->last_exists = exists;
return happened ? WATCHER_CHECK_EVENT : WATCHER_CHECK_NO_EVENT;
}

void DestroyFileWatcherState(void *state)
// =========== code for the file_deleted event ===========

WatcherCheckResult FileWatcherCheckFileDeleted(void *state)
{
assert(state != NULL);
FileWatcherState *fws = state;
if (fws != NULL)

bool exists;
if (!FileLstat(fws, &exists, NULL))
{
free(fws->path);
free(fws);
/* Unknown state, keep the last observation as is */
return WATCHER_CHECK_ERROR;
}

bool deleted = fws->last_exists && !exists;
return FileWatcherRecordObservation(fws, exists, deleted, "deletion");
}

4 changes: 2 additions & 2 deletions cf-reactor/file_watcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
#include <watcher.h> /* WatcherCheckResult */


void *FileWatcherStateNew(const char *path);
void *FileWatcherStateNew(const Rlist *args);
WatcherCheckResult FileWatcherCheckFileDeleted(void *state);
void DestroyFileWatcherState(void *state);
void FileWatcherStateDestroy(void *state);

#endif
Loading
Loading