Skip to content
Draft
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
511 changes: 453 additions & 58 deletions cf-reactor/file_watcher.c

Large diffs are not rendered by default.

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


void *FileWatcherStateNew(const char *path);
void *FileWatcherStateNew(const Rlist *args);
void *FileWatcherSizeStateNew(const Rlist *args);
void FileWatcherStateDestroy(void *state);

WatcherCheckResult FileWatcherCheckFileDeleted(void *state);
void DestroyFileWatcherState(void *state);
WatcherCheckResult FileWatcherCheckFileCreated(void *state);
WatcherCheckResult FileWatcherCheckFileModified(void *state);
WatcherCheckResult FileWatcherCheckFileContentModified(void *state);
WatcherCheckResult FileWatcherCheckFilePermissionsChanged(void *state);
WatcherCheckResult FileWatcherCheckFileOwnerChanged(void *state);
WatcherCheckResult FileWatcherCheckFileReplaced(void *state);
WatcherCheckResult FileWatcherCheckFileTypeChanged(void *state);
WatcherCheckResult FileWatcherCheckSymlinkTargetChanged(void *state);
WatcherCheckResult FileWatcherCheckFileSizeExceeds(void *state);

#endif
206 changes: 175 additions & 31 deletions cf-reactor/reactor_transform.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,16 @@
#include <verify_classes.h>
#include <verify_vars.h>
#include <watcher.h>
#include <file_watcher.h>
#include <agent_operations.h> // ScheduleAgentOperations()
#include <attributes.h> // GetClassContextAttributes()
#include <conn_cache.h> // ConnCache_Init(), ConnCache_Destroy()
#include <mod_custom.h> // Initialize/FinalizeCustomPromises()
#include <timeout.h> // SetReferenceTime()
#include <time_classes.h> // UpdateTimeClasses()
#include <mod_reactor.h>
#include <syntax.h> // PromiseTypeSyntaxGet(), BodySyntaxGetConstraintSyntax()
#include <file_watcher.h>
#include <conversion.h>

/* Promise types evaluated within `bundle reactor NAME { ... }`. */
static const char *const REACTOR_TYPESEQUENCE[] =
Expand Down Expand Up @@ -98,18 +101,6 @@ static const Bundle *ResolveThenBundle(
return bp;
}

/* Records the outcome of an iteration of an events promise: defines the
* classes of its 'classes' body, and logs it like cf-agent does for its
* promises. */
static void RecordEventsPromiseOutcome(EvalContext *ctx, const Promise *pp, PromiseResult result)
{
assert(pp != NULL);

Attributes a = GetClassContextAttributes(ctx, pp);
ClassAuditLog(ctx, pp, &a, result);
EvalContextLogPromiseIterationOutcome(ctx, pp, result);
}

/* Returns the list of bundle references of the (expanded) 'then' attribute,
* which is always a list after expansion, see ExpandBundleListReference().
* Logs an error and returns NULL if there is no 'then' attribute or it is
Expand All @@ -134,21 +125,160 @@ static const Rlist *GetThenBundles(const Promise *pp)
return bundles;
}

// Temporary limitations:
// - 'when' body: only a single constraint, 'file_deleted', is supported (no OR-ing of constraints yet)
// TODO: support multiple 'when' constraints
static PromiseResult KeepEventsPromise(EvalContext *ctx, const Promise *pp)
/*****************************************************************************/

typedef struct
{
const char *name;
const char *const *extra_args;
WatcherStateNewFn new_state;
WatcherStateDestroyFn destroy_state;
WatcherCheckFn check;
} EventType;

static const char *const NO_EXTRA_ARGS[] =
{
NULL
};

static const char *const FILE_SIZE_EXCEEDS_ARGS[] =
{
REACTOR_SIZE_THRESHOLD,
NULL
};

#define EventTypeNew(name, args, new_state, destroy_state, check) \
{ name, args, new_state, destroy_state, check }
#define EventTypeNewNull() EventTypeNew(NULL, NULL, NULL, NULL, NULL)

/* A file event, about the file at the path of its constraint */
#define FileEventTypeNew(name, check) \
EventTypeNew(name, NO_EXTRA_ARGS, FileWatcherStateNew, FileWatcherStateDestroy, check)

static const EventType EVENT_TABLE[] =
{
FileEventTypeNew(REACTOR_FILE_DELETED, FileWatcherCheckFileDeleted),
FileEventTypeNew(REACTOR_FILE_CREATED, FileWatcherCheckFileCreated),
FileEventTypeNew(REACTOR_FILE_MODIFIED, FileWatcherCheckFileModified),
FileEventTypeNew(REACTOR_FILE_CONTENT_MODIFIED, FileWatcherCheckFileContentModified),
FileEventTypeNew(REACTOR_FILE_PERMISSIONS_CHANGED, FileWatcherCheckFilePermissionsChanged),
FileEventTypeNew(REACTOR_FILE_OWNER_CHANGED, FileWatcherCheckFileOwnerChanged),
FileEventTypeNew(REACTOR_FILE_REPLACED, FileWatcherCheckFileReplaced),
FileEventTypeNew(REACTOR_FILE_TYPE_CHANGED, FileWatcherCheckFileTypeChanged),
FileEventTypeNew(REACTOR_SYMLINK_TARGET_CHANGED, FileWatcherCheckSymlinkTargetChanged),
EventTypeNew(REACTOR_FILE_SIZE_EXCEEDS, FILE_SIZE_EXCEEDS_ARGS,
FileWatcherSizeStateNew, FileWatcherStateDestroy, FileWatcherCheckFileSizeExceeds),
EventTypeNewNull()
};

static const EventType *EventsPromiseGetFirstEvent(const Promise *pp, Rlist **args)
{
assert(pp != NULL);
assert(args != NULL);

const char *path = PromiseGetConstraintAsRval(pp, "file_deleted", RVAL_TYPE_SCALAR);
if (path == NULL)
for (size_t i = 0; EVENT_TABLE[i].name != NULL; i++)
{
Log(LOG_LEVEL_WARNING,
"Reactor events promise '%s' must specify exactly one file to watch in its 'when' body, ignoring",
pp->promiser);
return PROMISE_RESULT_FAIL;
const Constraint *con = PromiseGetConstraint(pp, EVENT_TABLE[i].name);
if (con != NULL)
{
RlistAppend(args, con->rval.item, con->rval.type);
return &EVENT_TABLE[i];
}
}
Log(LOG_LEVEL_ERR,
"Reactor events promise '%s' does not specify an event to watch in its 'when' body",
pp->promiser);
return NULL;
}

static time_t DEFAULT_INTERVAL = 1;

static time_t EventsPromiseGetInterval(const Promise *pp)
{
const Constraint *interval_con = PromiseGetConstraint(pp, "interval");
if (interval_con == NULL)
{
return DEFAULT_INTERVAL;
}
return (time_t) IntFromString((char *) interval_con->rval.item);
}

static bool ResolveWhenBody(const Promise *pp, WatcherOptions *opt)
{
assert(pp != NULL);
assert(opt != NULL);
// 1. Get the event and its arg
Rlist *args = NULL;
const EventType *event = EventsPromiseGetFirstEvent(pp, &args);

if (event == NULL)
{
RlistDestroy(args);
return false;
}

// 2. if the event has additional args, add them to the list of args
// TODO: default values?
for (const char *const *extra_arg = event->extra_args; *extra_arg != NULL; extra_arg++)
{
const Constraint *con = PromiseGetConstraint(pp, *extra_arg);
if (con == NULL)
{
Log(LOG_LEVEL_ERR,
"Constraint '%s' is required for event '%s' in events promise '%s'",
*extra_arg, event->name, pp->promiser);
RlistDestroy(args);
return false;
}
RlistAppend(&args, con->rval.item, con->rval.type);
}

// 3. check that other constraints are not defined in the promise, that
// do not belong here
const PromiseTypeSyntax *events_syntax = PromiseTypeSyntaxGet("reactor", "events");
assert(events_syntax != NULL);
const ConstraintSyntax *when_syntax =
PromiseTypeSyntaxGetConstraintSyntax(events_syntax, "when")->range.body_type_syntax->constraints;

for (size_t i = 0; i < SeqLength(pp->conlist); i++)
{
const Constraint *cp = SeqAt(pp->conlist, i);
if (BodySyntaxGetConstraintSyntax(when_syntax, cp->lval) == NULL || /* not a 'when' constraint */
StringEqual(cp->lval, event->name) ||
StringEqual(cp->lval, "interval") ||
StringEqual(cp->lval, "meta")) /* also a promise attribute */
{
continue;
}

bool own_arg = false;
for (const char *const *extra_arg = event->extra_args; *extra_arg != NULL; extra_arg++)
{
own_arg = own_arg || StringEqual(*extra_arg, cp->lval);
}
if (!own_arg)
{
Log(LOG_LEVEL_ERR,
"Running mutually exclusive constraints for '%s' event type in '%s' events promise",
event->name, pp->promiser);
RlistDestroy(args);
return false;
}
}

opt->state = event->new_state(args);
opt->check_callback = event->check;
opt->destroy_state = event->destroy_state;
opt->interval = EventsPromiseGetInterval(pp);
RlistDestroy(args);
return true;
}

/*****************************************************************************/

static PromiseResult KeepEventsPromise(EvalContext *ctx, const Promise *pp)
{
assert(pp != NULL);

const Rlist *then_bundles = GetThenBundles(pp);
if (then_bundles == NULL)
Expand All @@ -164,19 +294,33 @@ static PromiseResult KeepEventsPromise(EvalContext *ctx, const Promise *pp)
}
}

WatcherOptions opt = {
.state = FileWatcherStateNew(path),
.check_callback = FileWatcherCheckFileDeleted,
.destroy_state = DestroyFileWatcherState,
};
WatcherOptions opt;
if (!ResolveWhenBody(pp, &opt))
{
return PROMISE_RESULT_FAIL;
}

// register watcher
Log(LOG_LEVEL_INFO, "Registering a file_deleted watcher for events promise '%s', on file '%s'", pp->promiser, path);
WatcherRegister(pp, opt, 1);
bool failed = WatcherRegister(pp, &opt);
if (failed)
{
return PROMISE_RESULT_FAIL;
}

return PROMISE_RESULT_NOOP;
}

/* Records the outcome of an iteration of an events promise: defines the
* classes of its 'classes' body, and logs it like cf-agent does for its
* promises. */
static void RecordEventsPromiseOutcome(EvalContext *ctx, const Promise *pp, PromiseResult result)
{
assert(pp != NULL);

Attributes a = GetClassContextAttributes(ctx, pp);
ClassAuditLog(ctx, pp, &a, result);
EvalContextLogPromiseIterationOutcome(ctx, pp, result);
}

static PromiseResult KeepReactorPromise(EvalContext *ctx, const Promise *pp, ARG_UNUSED void *param)
{
assert(param == NULL);
Expand Down
30 changes: 17 additions & 13 deletions cf-reactor/watcher.c
Original file line number Diff line number Diff line change
Expand Up @@ -118,15 +118,15 @@
}

// Expects interval to be strictly greater than 0, otherwise the watcher thread will busy spin
void WatcherRegister(const Promise *pp, WatcherOptions opt, time_t interval)
bool WatcherRegister(const Promise *pp, WatcherOptions *opt)
{
assert(pp != NULL);
assert(pp->promiser != NULL);
assert(opt.check_callback != NULL);
assert(opt.destroy_state != NULL);
assert(opt.state != NULL);
assert(opt->check_callback != NULL);
assert(opt->destroy_state != NULL);
assert(opt->state != NULL);
assert(opt->interval > 0);
assert(watchers != NULL);
assert(interval > 0);

ThreadLock(&watchers_mutex);
char promise_hash[CF_HOSTKEY_STRING_SIZE];
Expand All @@ -137,37 +137,41 @@
{
// ignore duplicates
ThreadUnlock(&watchers_mutex);
opt.destroy_state(opt.state);
return;
opt->destroy_state(opt->state);
return false;
}

Watcher *w = xmalloc(sizeof(Watcher));
w->promise_hash = xstrdup(promise_hash);
w->promise = pp->org_pp;
w->promiser = xstrdup(pp->promiser);
w->poll_interval_secs = interval;
w->poll_interval_secs = opt->interval;
w->next_due = 0; /* due immediately on the watcher thread's first pass */
w->last_check = WATCHER_CHECK_NO_EVENT;
w->check_callback = opt.check_callback;
w->destroy_state = opt.destroy_state;
w->check_callback = opt->check_callback;
w->destroy_state = opt->destroy_state;

// Take over the state of an identical watcher
bool failed = false;
prev = (old_watchers != NULL) ? MapGet(old_watchers, promise_hash) : NULL;
if (prev == NULL)
{
w->state = opt.state;
w->state = opt->state;
WatcherCheckResult check = w->check_callback(w->state);
failed = (check == WATCHER_CHECK_ERROR);
}
else
{
Log(LOG_LEVEL_DEBUG, "Reactor watcher '%s' takes over the state of an identical watcher", w->promiser);
w->state = prev->state;
prev->state = NULL; /* so not destroyed with prev */
opt.destroy_state(opt.state);
opt->destroy_state(opt->state);
}

MapInsert(watchers, w->promise_hash, w);

ThreadUnlock(&watchers_mutex);
return failed;
}

static void WatcherDestroy(void *item)
Expand Down Expand Up @@ -292,7 +296,7 @@
}
else
{
Log(LOG_LEVEL_NOTICE, "Reactor watcher '%s' fired", w->promiser);
Log(LOG_LEVEL_VERBOSE, "Reactor watcher '%s' fired", w->promiser);
}
on_event(ctx, w->promise, w->promise_hash, event->check);
free(event);
Expand Down
17 changes: 11 additions & 6 deletions cf-reactor/watcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ typedef enum
WATCHER_CHECK_ERROR, /* unable to tell whether the event happened */
} WatcherCheckResult;

typedef void *(*WatcherStateNewFn)(const Rlist *args);
typedef WatcherCheckResult (*WatcherCheckFn)(void *state);
typedef void (*WatcherStateDestroyFn)(void *state);

Expand All @@ -46,6 +47,7 @@ typedef struct
WatcherCheckFn check_callback; /* whether the event happened, updating state */
WatcherStateDestroyFn destroy_state;
void *state; /* owned by the registry once registered */
time_t interval; /* seconds between two checks, > 0 */
} WatcherOptions;

/**
Expand Down Expand Up @@ -85,18 +87,21 @@ void WatcherRegistryFinalize(void);
*
* If a watcher of the previous policy (see EventWatcherPause()) was
* registered for an identical expanded promise, the new watcher takes over
* its state instead of opt.state, so that the events happening while paused
* are detected.
* its state instead of opt->state, so that the events happening while paused
* are detected. Otherwise opt->state is checked once right away, as the
* reference for the next checks, so that only what happens from now on
* fires.
*
* @param pp the expanded events promise. Its hash and its unexpanded
* promise (pp->org_pp) are passed back to the WatcherEventFn on
* event. The unexpanded promise is not owned, so the policy
* holding it must not be destroyed before EventWatcherPause().
* @param opt what the watcher checks for, see WatcherOptions. opt.state is
* owned by the registry.
* @param interval interval between runs
* @param opt what the watcher checks for, see WatcherOptions. opt->state is
* owned by the registry, which destroys it right away if not
* used, so it must not be used by the caller afterwards.
* opt->interval must be strictly greater than 0.
*/
void WatcherRegister(const Promise *pp, WatcherOptions opt, time_t interval);
bool WatcherRegister(const Promise *pp, WatcherOptions *opt);
bool EventWatcherInitialize(int *fd);
void EventWatcherHandleEvents(EvalContext *ctx, WatcherEventFn on_event, int fd, fd_set *readfds);

Expand Down
Loading
Loading