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
96 changes: 67 additions & 29 deletions cf-reactor/reactor_transform.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ static const char *const REACTOR_TYPESEQUENCE[] =
NULL
};

/* Resolves the agent or common bundle referred to by an events promise's
* 'then' attribute, either `then => "name"` or `then => name(args)`. If args is
* not NULL, it is set to the bundle arguments (or NULL if there are none).
/* Resolves the agent or common bundle referred to by an entry of an events
* promise's 'then' attribute, either "name" or name(args). If args is not
* NULL, it is set to the bundle arguments (or NULL if there are none).
* Logs an error and returns NULL if the bundle is not found. */
static const Bundle *ResolveThenBundle(
const EvalContext *ctx, const Promise *pp, Rval then_rval, const Rlist **args)
Expand Down Expand Up @@ -110,10 +110,33 @@ static void RecordEventsPromiseOutcome(EvalContext *ctx, const Promise *pp, Prom
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
* empty. */
static const Rlist *GetThenBundles(const Promise *pp)
{
assert(pp != NULL);

const Constraint *then_constraint = PromiseGetConstraint(pp, "then");
if (then_constraint == NULL)
{
Log(LOG_LEVEL_ERR, "Reactor events promise '%s' does not specify a 'then' bundle", pp->promiser);
return NULL;
}

assert(then_constraint->rval.type == RVAL_TYPE_LIST);
const Rlist *bundles = RvalRlistValue(then_constraint->rval);
if (bundles == NULL)
{
Log(LOG_LEVEL_ERR, "Reactor events promise '%s' has an empty list of 'then' bundles", pp->promiser);
}
return bundles;
}

// Temporary limitations:
// - 'when' body: only a single constraint, 'file_deleted', is supported (no OR-ing of constraints yet)
// - 'then': only a single bundle is supported, not a list of bundles yet
// TODO: support multiple 'when' constraints and a list of 'then' bundles
// TODO: support multiple 'when' constraints
static PromiseResult KeepEventsPromise(EvalContext *ctx, const Promise *pp)
{
assert(pp != NULL);
Expand All @@ -127,16 +150,18 @@ static PromiseResult KeepEventsPromise(EvalContext *ctx, const Promise *pp)
return PROMISE_RESULT_FAIL;
}

const Constraint *then_constraint = PromiseGetConstraint(pp, "then");
if (then_constraint == NULL)
const Rlist *then_bundles = GetThenBundles(pp);
if (then_bundles == NULL)
{
Log(LOG_LEVEL_ERR, "Reactor events promise '%s' does not specify a 'then' bundle, ignoring", pp->promiser);
return PROMISE_RESULT_FAIL;
}

if (ResolveThenBundle(ctx, pp, then_constraint->rval, NULL) == NULL)
for (const Rlist *rp = then_bundles; rp != NULL; rp = rp->next)
{
return PROMISE_RESULT_FAIL;
if (ResolveThenBundle(ctx, pp, rp->val, NULL) == NULL)
{
return PROMISE_RESULT_FAIL;
}
}

WatcherOptions opt = {
Expand Down Expand Up @@ -205,33 +230,19 @@ static void EvaluateReactorBundle(EvalContext *ctx, const Bundle *bp)
EvalContextStackPopFrame(ctx);
}

/* Runs the bundle referred to by the 'then' attribute of the (expanded)
* events promise. */
static PromiseResult RunThenBundle(EvalContext *ctx, const Promise *pp)
/* Runs the bundle referred to by an entry of the 'then' attribute of the
* (expanded) events promise. */
static PromiseResult RunThenBundle(EvalContext *ctx, const Promise *pp, Rval then_rval)
{
assert(pp != NULL);

const Constraint *then_constraint = PromiseGetConstraint(pp, "then");
if (then_constraint == NULL)
{
Log(LOG_LEVEL_ERR, "Reactor events promise '%s' does not specify a 'then' bundle", pp->promiser);
return PROMISE_RESULT_FAIL;
}

const Rlist *args = NULL;
const Bundle *bp = ResolveThenBundle(ctx, pp, then_constraint->rval, &args);
const Bundle *bp = ResolveThenBundle(ctx, pp, then_rval, &args);
if (bp == NULL)
{
return PROMISE_RESULT_FAIL;
}

/* The promise lock cache makes each promise act at most once per
* EvalContext, and the function cache keeps the results of functions like
* execresult(), but cf-reactor keeps the same EvalContext across events.
* Clear them so every event gets a fresh run of the bundle. */
EvalContextPromiseLockCacheClear(ctx);
EvalContextFunctionCacheClear(ctx);

BundleBanner(bp, args);
EvalContextSetBundleArgs(ctx, args);
EvalContextStackPushBundleFrame(ctx, bp, args, false, NULL);
Expand All @@ -254,6 +265,33 @@ static PromiseResult RunThenBundle(EvalContext *ctx, const Promise *pp)
return result;
}

/* Runs the bundles referred to by the 'then' attribute of the (expanded)
* events promise in order, like a bundlesequence. */
static PromiseResult RunThenBundles(EvalContext *ctx, const Promise *pp)
{
assert(pp != NULL);

const Rlist *then_bundles = GetThenBundles(pp);
if (then_bundles == NULL)
{
return PROMISE_RESULT_FAIL;
}

/* The promise lock cache makes each promise act at most once per
* EvalContext, and the function cache keeps the results of functions like
* execresult(), but cf-reactor keeps the same EvalContext across events.
* Clear them so every event gets a fresh run of the bundles. */
EvalContextPromiseLockCacheClear(ctx);
EvalContextFunctionCacheClear(ctx);

PromiseResult result = PROMISE_RESULT_NOOP;
for (const Rlist *rp = then_bundles; rp != NULL; rp = rp->next)
{
result = PromiseResultUpdate(result, RunThenBundle(ctx, pp, rp->val));
}
return result;
}

typedef struct
{
const char *promise_hash;
Expand All @@ -276,7 +314,7 @@ static PromiseResult KeepEventsPromiseOnEvent(EvalContext *ctx, const Promise *p
return PROMISE_RESULT_SKIPPED;
}

return RunThenBundle(ctx, pp);
return RunThenBundles(ctx, pp);
}

void HandleReactorEvent(EvalContext *ctx, const Promise *pp, const char *promise_hash, WatcherCheckResult check)
Expand Down
1 change: 1 addition & 0 deletions libpromises/cf3.defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@ typedef enum
CF_DATA_TYPE_REAL_RANGE,
CF_DATA_TYPE_COUNTER,
CF_DATA_TYPE_CONTAINER,
CF_DATA_TYPE_BUNDLE_LIST,
CF_DATA_TYPE_NONE
} DataType;

Expand Down
3 changes: 2 additions & 1 deletion libpromises/cf3parse_logic.h
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,8 @@ static SyntaxTypeMatch CheckConstraint(
/* For bodies and bundles definitions can be elsewhere, so
they are checked in PolicyCheckRunnable(). */
if (bs[l].dtype != CF_DATA_TYPE_BODY
&& bs[l].dtype != CF_DATA_TYPE_BUNDLE)
&& bs[l].dtype != CF_DATA_TYPE_BUNDLE
&& bs[l].dtype != CF_DATA_TYPE_BUNDLE_LIST)
{
return CheckConstraintTypeMatch(
lval,
Expand Down
1 change: 1 addition & 0 deletions libpromises/conversion.c
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ static const char *const datatype_strings[] =
[CF_DATA_TYPE_REAL_RANGE] = "rrange",
[CF_DATA_TYPE_COUNTER] = "counter",
[CF_DATA_TYPE_CONTAINER] = "data",
[CF_DATA_TYPE_BUNDLE_LIST] = "blist",
[CF_DATA_TYPE_NONE] = "none"
};

Expand Down
33 changes: 33 additions & 0 deletions libpromises/expand.c
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,39 @@ Rval ExpandBundleReference(EvalContext *ctx,
return RvalNew(NULL, RVAL_TYPE_NOPROMISEE);
}

/**
* Expand a bundle reference or a list of bundle references into a list of
* bundle references, like in a bundlesequence. Naked list variables (@(list))
* are flattened into the result. Unlike EvaluateFinalRval(), function call
* entries are bundle calls and are never evaluated as functions.
*/
Rval ExpandBundleListReference(EvalContext *ctx,
const char *ns, const char *scope,
Rval rval)
{
// Allocates new memory for the copy
switch (rval.type)
{
case RVAL_TYPE_SCALAR:
case RVAL_TYPE_FNCALL:
{
const Rlist list = { .val = rval, .next = NULL };
return (Rval) { ExpandList(ctx, ns, scope, &list, true), RVAL_TYPE_LIST };
}

case RVAL_TYPE_LIST:
return (Rval) { ExpandList(ctx, ns, scope, RvalRlistValue(rval), true),
RVAL_TYPE_LIST };

case RVAL_TYPE_CONTAINER:
case RVAL_TYPE_NOPROMISEE:
return RvalNew(NULL, RVAL_TYPE_NOPROMISEE);
}

assert(false);
return RvalNew(NULL, RVAL_TYPE_NOPROMISEE);
}

/**
* Expand a #string into Buffer #out, returning the pointer to the string
* itself, inside the Buffer #out. If #out is NULL then the buffer will be
Expand Down
1 change: 1 addition & 0 deletions libpromises/expand.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ bool IsExpandable(const char *str);
char *ExpandScalar(const EvalContext *ctx, const char *ns, const char *scope,
const char *string, Buffer *out);
Rval ExpandBundleReference(EvalContext *ctx, const char *ns, const char *scope, Rval rval);
Rval ExpandBundleListReference(EvalContext *ctx, const char *ns, const char *scope, Rval rval);
Rval ExpandPrivateRval(const EvalContext *ctx, const char *ns, const char *scope, const void *rval_item, RvalType rval_type);
Rlist *ExpandList(const EvalContext *ctx, const char *ns, const char *scope, const Rlist *list, int expandnaked);
Rval EvaluateFinalRval(EvalContext *ctx, const Policy *policy, const char *ns, const char *scope, Rval rval, bool forcelist, const Promise *pp);
Expand Down
2 changes: 1 addition & 1 deletion libpromises/mod_reactor.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ static const BodySyntax when_body = BodySyntaxNew("when", when_constraints, NULL
static const ConstraintSyntax CF_EVENT_BODIES[] =
{
ConstraintSyntaxNewBody("when", &when_body, "Event to react to", SYNTAX_STATUS_NORMAL),
ConstraintSyntaxNewBundle("then", "Bundle to run on event", SYNTAX_STATUS_NORMAL),
ConstraintSyntaxNewBundleList("then", "Bundle or list of bundles to run in order on event", SYNTAX_STATUS_NORMAL),
ConstraintSyntaxNewNull()
};

Expand Down
69 changes: 44 additions & 25 deletions libpromises/policy.c
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,7 @@ static bool RvalTypeCheckDataType(RvalType rval_type, DataType expected_datatype
case CF_DATA_TYPE_OPTION_LIST:
case CF_DATA_TYPE_REAL_LIST:
case CF_DATA_TYPE_STRING_LIST:
case CF_DATA_TYPE_BUNDLE_LIST:
return (rval_type == RVAL_TYPE_SCALAR) || (rval_type == RVAL_TYPE_LIST);

case CF_DATA_TYPE_CONTAINER:
Expand Down Expand Up @@ -2730,6 +2731,42 @@ static bool ValidateCustomPromise(const Promise *pp, Seq *errors)
return valid;
}

static bool BundleCallCheckArity(const Promise *pp, const Constraint *cp, Rval rval, Seq *errors)
{
if (rval.type != RVAL_TYPE_FNCALL)
{
return true;
}

// HACK: exploiting the fact that class-references and call-references are similar
FnCall *call = RvalFnCallValue(rval);
ClassRef ref = ClassRefParse(call->name);
if (!ClassRefIsQualified(ref))
{
ClassRefQualify(&ref, PromiseGetNamespace(pp));
}

const Bundle *callee = PolicyGetBundle(PolicyFromPromise(pp), ref.ns, "agent", ref.name);
if (!callee)
{
callee = PolicyGetBundle(PolicyFromPromise(pp), ref.ns, "common", ref.name);
}

ClassRefDestroy(ref);

if (callee != NULL)
{
if (RlistLen(call->args) != RlistLen(callee->args))
{
SeqAppend(errors, PolicyErrorNew(POLICY_ELEMENT_TYPE_CONSTRAINT, cp,
POLICY_ERROR_BUNDLE_CALL_ARITY,
call->name, RlistLen(callee->args), RlistLen(call->args)));
return false;
}
}
return true;
}

bool PromiseCheckBundleCallArity(const Promise *pp, const char *lval, Seq *errors)
{
assert(pp != NULL);
Expand All @@ -2744,35 +2781,17 @@ bool PromiseCheckBundleCallArity(const Promise *pp, const char *lval, Seq *error
// ensure: if call and callee are resolved, then they have matching arity
if (StringEqual(cp->lval, lval))
{
if (cp->rval.type == RVAL_TYPE_FNCALL)
if (cp->rval.type == RVAL_TYPE_LIST)
{
// HACK: exploiting the fact that class-references and call-references are similar
FnCall *call = RvalFnCallValue(cp->rval);
ClassRef ref = ClassRefParse(call->name);
if (!ClassRefIsQualified(ref))
for (const Rlist *rp = RvalRlistValue(cp->rval); rp != NULL; rp = rp->next)
{
ClassRefQualify(&ref, PromiseGetNamespace(pp));
}

const Bundle *callee = PolicyGetBundle(PolicyFromPromise(pp), ref.ns, "agent", ref.name);
if (!callee)
{
callee = PolicyGetBundle(PolicyFromPromise(pp), ref.ns, "common", ref.name);
}

ClassRefDestroy(ref);

if (callee)
{
if (RlistLen(call->args) != RlistLen(callee->args))
{
SeqAppend(errors, PolicyErrorNew(POLICY_ELEMENT_TYPE_CONSTRAINT, cp,
POLICY_ERROR_BUNDLE_CALL_ARITY,
call->name, RlistLen(callee->args), RlistLen(call->args)));
success = false;
}
success &= BundleCallCheckArity(pp, cp, rp->val, errors);
}
}
else
{
success &= BundleCallCheckArity(pp, cp, cp->rval, errors);
}
}
}
return success;
Expand Down
8 changes: 7 additions & 1 deletion libpromises/promises.c
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,7 @@ static void AddDefaultBodiesToPromise(EvalContext *ctx, Promise *promise, const

static bool EvaluateConstraintIteration(EvalContext *ctx, const Constraint *cp, Rval *rval_out)
{
assert(cp != NULL);
assert(cp->type == POLICY_ELEMENT_TYPE_PROMISE);
const Promise *pp = cp->parent.promise;

Expand All @@ -569,10 +570,15 @@ static bool EvaluateConstraintIteration(EvalContext *ctx, const Constraint *cp,
return false;
}

if (ExpectedDataType(cp->lval) == CF_DATA_TYPE_BUNDLE)
const DataType expected_type = ExpectedDataType(cp->lval);
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
if (expected_type == CF_DATA_TYPE_BUNDLE)
{
*rval_out = ExpandBundleReference(ctx, NULL, "this", cp->rval);
}
else if (expected_type == CF_DATA_TYPE_BUNDLE_LIST)
{
*rval_out = ExpandBundleListReference(ctx, NULL, "this", cp->rval);
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
}
else
{
*rval_out = EvaluateFinalRval(ctx, PromiseGetPolicy(pp), NULL,
Expand Down
1 change: 1 addition & 0 deletions libpromises/rlist.c
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ RvalType DataTypeToRvalType(DataType datatype)
case CF_DATA_TYPE_OPTION_LIST:
case CF_DATA_TYPE_REAL_LIST:
case CF_DATA_TYPE_STRING_LIST:
case CF_DATA_TYPE_BUNDLE_LIST:
return RVAL_TYPE_LIST;

case CF_DATA_TYPE_CONTAINER:
Expand Down
Loading
Loading