static Rlist *NewExpArgs(EvalContext *ctx, const Policy *policy, const FnCall *fp) { { const FnCallType *fn = FnCallTypeGet(fp->name); int len = RlistLen(fp->args); if (!(fn->options & FNCALL_OPTION_VARARG)) { if (len != FnNumArgs(fn)) { Log(LOG_LEVEL_ERR, "Arguments to function '%s' do not tally. Expected %d not %d", fp->name, FnNumArgs(fn), len); PromiseRef(LOG_LEVEL_ERR, fp->caller); exit(EXIT_FAILURE); } } } Rlist *expanded_args = NULL; for (const Rlist *rp = fp->args; rp != NULL; rp = rp->next) { Rval rval; switch (rp->val.type) { case RVAL_TYPE_FNCALL: { FnCall *subfp = RlistFnCallValue(rp); rval = FnCallEvaluate(ctx, policy, subfp, fp->caller).rval; assert(rval.item); } break; default: rval = ExpandPrivateRval(ctx, NULL, NULL, rp->val.item, rp->val.type); assert(rval.item); break; } RlistAppend(&expanded_args, rval.item, rval.type); RvalDestroy(rval); } return expanded_args; }
void ScopePutMatch(int index, const char *value) { if (!SCOPE_MATCH) { SCOPE_MATCH = ScopeNew("match"); } Scope *ptr = SCOPE_MATCH; char lval[4] = { 0 }; snprintf(lval, 3, "%d", index); Rval rval = (Rval) { value, RVAL_TYPE_SCALAR }; CfAssoc *assoc = HashLookupElement(ptr->hashtable, lval); if (assoc) { if (CompareVariableValue(rval, assoc) == 0) { /* Identical value, keep as is */ } else { /* Different value, bark and replace */ if (!UnresolvedVariables(assoc, RVAL_TYPE_SCALAR)) { CfOut(OUTPUT_LEVEL_INFORM, "", " !! Duplicate selection of value for variable \"%s\" in scope %s", lval, ptr->scope); } RvalDestroy(assoc->rval); assoc->rval = RvalCopy(rval); assoc->dtype = DATA_TYPE_STRING; CfDebug("Stored \"%s\" in context %s\n", lval, "match"); } } else { if (!HashInsertElement(ptr->hashtable, lval, rval, DATA_TYPE_STRING)) { ProgrammingError("Hash table is full"); } } }
void RlistDestroy(Rlist *list) /* Delete an rlist and all its references */ { Rlist *rl, *next; if (list != NULL) { for (rl = list; rl != NULL; rl = next) { next = rl->next; if (rl->item != NULL) { RvalDestroy((Rval) {rl->item, rl->type}); } free(rl); } } }
Rlist *NewExpArgs(EvalContext *ctx, const FnCall *fp, const Promise *pp) { int len; Rval rval; Rlist *newargs = NULL; FnCall *subfp; const FnCallType *fn = FnCallTypeGet(fp->name); len = RlistLen(fp->args); if (!fn->varargs) { if (len != FnNumArgs(fn)) { CfOut(OUTPUT_LEVEL_ERROR, "", "Arguments to function %s(.) do not tally. Expect %d not %d", fp->name, FnNumArgs(fn), len); PromiseRef(OUTPUT_LEVEL_ERROR, pp); exit(1); } } for (const Rlist *rp = fp->args; rp != NULL; rp = rp->next) { switch (rp->type) { case RVAL_TYPE_FNCALL: subfp = (FnCall *) rp->item; rval = FnCallEvaluate(ctx, subfp, pp).rval; break; default: rval = ExpandPrivateRval(ScopeGetCurrent()->scope, (Rval) {rp->item, rp->type}); break; } CfDebug("EXPARG: %s.%s\n", ScopeGetCurrent()->scope, (char *) rval.item); RlistAppend(&newargs, rval.item, rval.type); RvalDestroy(rval); } return newargs; }
static void ResolveControlBody(EvalContext *ctx, GenericAgentConfig *config, const Body *control_body) { const ConstraintSyntax *body_syntax = NULL; Rval returnval; assert(strcmp(control_body->name, "control") == 0); for (int i = 0; CONTROL_BODIES[i].constraints != NULL; i++) { body_syntax = CONTROL_BODIES[i].constraints; if (strcmp(control_body->type, CONTROL_BODIES[i].body_type) == 0) { break; } } if (body_syntax == NULL) { FatalError(ctx, "Unknown agent"); } char scope[CF_BUFSIZE]; snprintf(scope, CF_BUFSIZE, "%s_%s", control_body->name, control_body->type); Log(LOG_LEVEL_DEBUG, "Initiate control variable convergence for scope '%s'", scope); EvalContextStackPushBodyFrame(ctx, NULL, control_body, NULL); for (size_t i = 0; i < SeqLength(control_body->conlist); i++) { Constraint *cp = SeqAt(control_body->conlist, i); if (!IsDefinedClass(ctx, cp->classes)) { continue; } if (strcmp(cp->lval, CFG_CONTROLBODY[COMMON_CONTROL_BUNDLESEQUENCE].lval) == 0) { returnval = ExpandPrivateRval(ctx, NULL, scope, cp->rval.item, cp->rval.type); } else { returnval = EvaluateFinalRval(ctx, control_body->parent_policy, NULL, scope, cp->rval, true, NULL); } VarRef *ref = VarRefParseFromScope(cp->lval, scope); EvalContextVariableRemove(ctx, ref); if (!EvalContextVariablePut(ctx, ref, returnval.item, ConstraintSyntaxGetDataType(body_syntax, cp->lval), "source=promise")) { Log(LOG_LEVEL_ERR, "Rule from %s at/before line %zu", control_body->source_path, cp->offset.line); } VarRefDestroy(ref); if (strcmp(cp->lval, CFG_CONTROLBODY[COMMON_CONTROL_OUTPUT_PREFIX].lval) == 0) { strncpy(VPREFIX, returnval.item, CF_MAXVARSIZE); } if (strcmp(cp->lval, CFG_CONTROLBODY[COMMON_CONTROL_DOMAIN].lval) == 0) { strcpy(VDOMAIN, cp->rval.item); Log(LOG_LEVEL_VERBOSE, "SET domain = %s", VDOMAIN); EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_SYS, "domain"); EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_SYS, "fqhost"); snprintf(VFQNAME, CF_MAXVARSIZE, "%s.%s", VUQNAME, VDOMAIN); EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "fqhost", VFQNAME, DATA_TYPE_STRING, "inventory,source=agent,group=Host name"); EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "domain", VDOMAIN, DATA_TYPE_STRING, "source=agent"); EvalContextClassPutHard(ctx, VDOMAIN, "source=agent"); } if (strcmp(cp->lval, CFG_CONTROLBODY[COMMON_CONTROL_IGNORE_MISSING_INPUTS].lval) == 0) { Log(LOG_LEVEL_VERBOSE, "SET ignore_missing_inputs %s", RvalScalarValue(cp->rval)); config->ignore_missing_inputs = BooleanFromString(cp->rval.item); } if (strcmp(cp->lval, CFG_CONTROLBODY[COMMON_CONTROL_IGNORE_MISSING_BUNDLES].lval) == 0) { Log(LOG_LEVEL_VERBOSE, "SET ignore_missing_bundles %s", RvalScalarValue(cp->rval)); config->ignore_missing_bundles = BooleanFromString(cp->rval.item); } if (strcmp(cp->lval, CFG_CONTROLBODY[COMMON_CONTROL_CACHE_SYSTEM_FUNCTIONS].lval) == 0) { Log(LOG_LEVEL_VERBOSE, "SET cache_system_functions %s", RvalScalarValue(cp->rval)); bool cache_system_functions = BooleanFromString(RvalScalarValue(cp->rval)); EvalContextSetEvalOption(ctx, EVAL_OPTION_CACHE_SYSTEM_FUNCTIONS, cache_system_functions); } if (strcmp(cp->lval, CFG_CONTROLBODY[COMMON_CONTROL_GOALPATTERNS].lval) == 0) { /* Ignored */ continue; } RvalDestroy(returnval); } EvalContextStackPopFrame(ctx); }
void ScopeDeRefListsInHashtable(char *scope, Rlist *namelist, Rlist *dereflist) // Go through scope and for each variable in name-list, replace with a // value from the deref "lol" (list of lists) clock { int len; Scope *ptr; Rlist *rp; CfAssoc *cplist; AssocHashTableIterator i; CfAssoc *assoc; if ((len = RlistLen(namelist)) != RlistLen(dereflist)) { CfOut(OUTPUT_LEVEL_ERROR, "", " !! Name list %d, dereflist %d\n", len, RlistLen(dereflist)); ProgrammingError("Software Error DeRefLists... correlated lists not same length"); } if (len == 0) { return; } ptr = ScopeGet(scope); i = HashIteratorInit(ptr->hashtable); while ((assoc = HashIteratorNext(&i))) { for (rp = dereflist; rp != NULL; rp = rp->next) { cplist = (CfAssoc *) rp->item; if (strcmp(cplist->lval, assoc->lval) == 0) { /* Link up temp hash to variable lol */ if (rp->state_ptr == NULL || rp->state_ptr->type == RVAL_TYPE_FNCALL) { /* Unexpanded function, or blank variable must be skipped. */ return; } if (rp->state_ptr) { CfDebug("Rewriting expanded type for %s from %s to %s\n", assoc->lval, CF_DATATYPES[assoc->dtype], (char *) rp->state_ptr->item); // must first free existing rval in scope, then allocate new (should always be string) RvalDestroy(assoc->rval); // avoids double free - borrowing value from lol (freed in DeleteScope()) assoc->rval.item = xstrdup(rp->state_ptr->item); } switch (assoc->dtype) { case DATA_TYPE_STRING_LIST: assoc->dtype = DATA_TYPE_STRING; assoc->rval.type = RVAL_TYPE_SCALAR; break; case DATA_TYPE_INT_LIST: assoc->dtype = DATA_TYPE_INT; assoc->rval.type = RVAL_TYPE_SCALAR; break; case DATA_TYPE_REAL_LIST: assoc->dtype = DATA_TYPE_REAL; assoc->rval.type = RVAL_TYPE_SCALAR; break; default: /* Only lists need to be converted */ break; } CfDebug(" to %s\n", CF_DATATYPES[assoc->dtype]); } } } }
void ScopeAugment(EvalContext *ctx, const Bundle *bp, const Rlist *arguments) { if (RlistLen(bp->args) != RlistLen(arguments)) { CfOut(OUTPUT_LEVEL_ERROR, "", "While constructing scope \"%s\"\n", bp->name); fprintf(stderr, "Formal = "); RlistShow(stderr, bp->args); fprintf(stderr, ", Actual = "); RlistShow(stderr, arguments); fprintf(stderr, "\n"); FatalError(ctx, "Augment scope, formal and actual parameter mismatch is fatal"); } for (const Rlist *rpl = bp->args, *rpr = arguments; rpl != NULL; rpl = rpl->next, rpr = rpr->next) { const char *lval = rpl->item; CfOut(OUTPUT_LEVEL_VERBOSE, "", " ? Augment scope %s with %s (%c)\n", bp->name, lval, rpr->type); // CheckBundleParameters() already checked that there is no namespace collision // By this stage all functions should have been expanded, so we only have scalars left if (IsNakedVar(rpr->item, '@')) { DataType vtype; char qnaked[CF_MAXVARSIZE]; char naked[CF_BUFSIZE]; GetNaked(naked, rpr->item); if (IsQualifiedVariable(naked) && strchr(naked, CF_NS) == NULL) { snprintf(qnaked, CF_MAXVARSIZE, "%s%c%s", bp->ns, CF_NS, naked); } Rval retval; EvalContextVariableGet(ctx, (VarRef) { NULL, bp->name, qnaked }, &retval, &vtype); switch (vtype) { case DATA_TYPE_STRING_LIST: case DATA_TYPE_INT_LIST: case DATA_TYPE_REAL_LIST: ScopeNewList(ctx, (VarRef) { NULL, bp->name, lval }, RvalCopy((Rval) { retval.item, RVAL_TYPE_LIST}).item, DATA_TYPE_STRING_LIST); break; default: CfOut(OUTPUT_LEVEL_ERROR, "", " !! List parameter \"%s\" not found while constructing scope \"%s\" - use @(scope.variable) in calling reference", qnaked, bp->name); ScopeNewScalar(ctx, (VarRef) { NULL, bp->name, lval }, rpr->item, DATA_TYPE_STRING); break; } } else { switch(rpr->type) { case RVAL_TYPE_SCALAR: ScopeNewScalar(ctx, (VarRef) { NULL, bp->name, lval }, rpr->item, DATA_TYPE_STRING); break; case RVAL_TYPE_FNCALL: { FnCall *subfp = rpr->item; Promise *pp = NULL; // This argument should really get passed down. Rval rval = FnCallEvaluate(ctx, subfp, pp).rval; if (rval.type == RVAL_TYPE_SCALAR) { ScopeNewScalar(ctx, (VarRef) { NULL, bp->name, lval }, rval.item, DATA_TYPE_STRING); } else { CfOut(OUTPUT_LEVEL_ERROR, "", "Only functions returning scalars can be used as arguments"); } } break; default: ProgrammingError("An argument neither a scalar nor a list seemed to appear. Impossible"); } } } /* Check that there are no danglers left to evaluate in the hash table itself */ { Scope *ptr = ScopeGet(bp->name); AssocHashTableIterator i = HashIteratorInit(ptr->hashtable); CfAssoc *assoc = NULL; while ((assoc = HashIteratorNext(&i))) { Rval retval = ExpandPrivateRval(ctx, bp->name, assoc->rval); // Retain the assoc, just replace rval RvalDestroy(assoc->rval); assoc->rval = retval; } } return; }
bool EvalContextVariablePut(EvalContext *ctx, VarRef lval, Rval rval, DataType type) { Scope *ptr; const Rlist *rp; CfAssoc *assoc; if (rval.type == RVAL_TYPE_SCALAR) { CfDebug("AddVariableHash(%s.%s=%s (%s) rtype=%c)\n", lval.scope, lval.lval, (const char *) rval.item, CF_DATATYPES[type], rval.type); } else { CfDebug("AddVariableHash(%s.%s=(list) (%s) rtype=%c)\n", lval.scope, lval.lval, CF_DATATYPES[type], rval.type); } if (lval.lval == NULL || lval.scope == NULL) { CfOut(OUTPUT_LEVEL_ERROR, "", "scope.value = %s.%s", lval.scope, lval.lval); ProgrammingError("Bad variable or scope in a variable assignment, should not happen - forgotten to register a function call in fncall.c?"); } if (rval.item == NULL) { CfDebug("No value to assignment - probably a parameter in an unused bundle/body\n"); return false; } if (strlen(lval.lval) > CF_MAXVARSIZE) { char *lval_str = VarRefToString(lval); CfOut(OUTPUT_LEVEL_ERROR, "", "Variable %s cannot be added because its length exceeds the maximum length allowed: %d", lval_str, CF_MAXVARSIZE); free(lval_str); return false; } /* If we are not expanding a body template, check for recursive singularities */ if (strcmp(lval.scope, "body") != 0) { switch (rval.type) { case RVAL_TYPE_SCALAR: if (StringContainsVar((char *) rval.item, lval.lval)) { CfOut(OUTPUT_LEVEL_ERROR, "", "Scalar variable %s.%s contains itself (non-convergent): %s", lval.scope, lval.lval, (char *) rval.item); return false; } break; case RVAL_TYPE_LIST: for (rp = rval.item; rp != NULL; rp = rp->next) { if (StringContainsVar((char *) rp->item, lval.lval)) { CfOut(OUTPUT_LEVEL_ERROR, "", "List variable %s contains itself (non-convergent)", lval.lval); return false; } } break; default: break; } } ptr = ScopeGet(lval.scope); if (!ptr) { ptr = ScopeNew(lval.scope); if (!ptr) { return false; } } // Look for outstanding lists in variable rvals if (THIS_AGENT_TYPE == AGENT_TYPE_COMMON) { Rlist *listvars = NULL; if (ScopeGetCurrent() && strcmp(ScopeGetCurrent()->scope, "this") != 0) { MapIteratorsFromRval(ScopeGetCurrent()->scope, &listvars, rval); if (listvars != NULL) { CfOut(OUTPUT_LEVEL_ERROR, "", " !! Redefinition of variable \"%s\" (embedded list in RHS) in context \"%s\"", lval.lval, ScopeGetCurrent()->scope); } RlistDestroy(listvars); } } assoc = HashLookupElement(ptr->hashtable, lval.lval); if (assoc) { if (CompareVariableValue(rval, assoc) == 0) { /* Identical value, keep as is */ } else { /* Different value, bark and replace */ if (!UnresolvedVariables(assoc, rval.type)) { CfOut(OUTPUT_LEVEL_INFORM, "", " !! Duplicate selection of value for variable \"%s\" in scope %s", lval.lval, ptr->scope); } RvalDestroy(assoc->rval); assoc->rval = RvalCopy(rval); assoc->dtype = type; CfDebug("Stored \"%s\" in context %s\n", lval.lval, lval.scope); } } else { if (!HashInsertElement(ptr->hashtable, lval.lval, rval, type)) { ProgrammingError("Hash table is full"); } } CfDebug("Added Variable %s in scope %s with value (omitted)\n", lval.lval, lval.scope); return true; }
bool EvalContextVariablePut(EvalContext *ctx, VarRef lval, Rval rval, DataType type) { assert(type != DATA_TYPE_NONE); if (lval.lval == NULL || lval.scope == NULL) { ProgrammingError("Bad variable or scope in a variable assignment. scope.value = %s.%s", lval.scope, lval.lval); } if (rval.item == NULL) { return false; } if (strlen(lval.lval) > CF_MAXVARSIZE) { char *lval_str = VarRefToString(lval, true); Log(LOG_LEVEL_ERR, "Variable '%s'' cannot be added because its length exceeds the maximum length allowed '%d' characters", lval_str, CF_MAXVARSIZE); free(lval_str); return false; } // If we are not expanding a body template, check for recursive singularities if (strcmp(lval.scope, "body") != 0) { switch (rval.type) { case RVAL_TYPE_SCALAR: if (StringContainsVar((char *) rval.item, lval.lval)) { Log(LOG_LEVEL_ERR, "Scalar variable '%s.%s' contains itself (non-convergent), value '%s'", lval.scope, lval.lval, (char *) rval.item); return false; } break; case RVAL_TYPE_LIST: for (const Rlist *rp = rval.item; rp != NULL; rp = rp->next) { if (StringContainsVar(rp->item, lval.lval)) { Log(LOG_LEVEL_ERR, "List variable '%s' contains itself (non-convergent)", lval.lval); return false; } } break; default: break; } } else { assert(STACK_FRAME_TYPE_BODY == LastStackFrame(ctx, 0)->type); } Scope *put_scope = ScopeGet(lval.scope); if (!put_scope) { put_scope = ScopeNew(lval.scope); if (!put_scope) { return false; } } // Look for outstanding lists in variable rvals if (THIS_AGENT_TYPE == AGENT_TYPE_COMMON) { Rlist *listvars = NULL; Rlist *scalars = NULL; // TODO what do we do with scalars? if (ScopeGetCurrent() && strcmp(ScopeGetCurrent()->scope, "this") != 0) { MapIteratorsFromRval(ctx, ScopeGetCurrent()->scope, &listvars, &scalars, rval); if (listvars != NULL) { Log(LOG_LEVEL_ERR, "Redefinition of variable '%s' (embedded list in RHS) in context '%s'", lval.lval, ScopeGetCurrent()->scope); } RlistDestroy(listvars); RlistDestroy(scalars); } } // FIX: lval is stored with array params as part of the lval for legacy reasons. char *final_lval = VarRefToString(lval, false); CfAssoc *assoc = HashLookupElement(put_scope->hashtable, final_lval); if (assoc) { if (CompareVariableValue(rval, assoc) != 0) { /* Different value, bark and replace */ if (!UnresolvedVariables(assoc, rval.type)) { Log(LOG_LEVEL_INFO, "Replaced value of variable '%s' in scope '%s'", lval.lval, put_scope->scope); } RvalDestroy(assoc->rval); assoc->rval = RvalCopy(rval); assoc->dtype = type; } } else { if (!HashInsertElement(put_scope->hashtable, final_lval, rval, type)) { ProgrammingError("Hash table is full"); } } free(final_lval); return true; }
void ScopeMapBodyArgs(EvalContext *ctx, const Body *body, const Rlist *args) { const Rlist *arg = NULL; const Rlist *param = NULL; for (arg = args, param = body->args; arg != NULL && param != NULL; arg = arg->next, param = param->next) { DataType arg_type = CF_DATA_TYPE_NONE; switch (arg->val.type) { case RVAL_TYPE_SCALAR: arg_type = StringDataType(ctx, RlistScalarValue(arg)); break; case RVAL_TYPE_FNCALL: { const FnCallType *fn = FnCallTypeGet(RlistFnCallValue(arg)->name); if (!fn) { FatalError(ctx, "Argument '%s' given to body '%s' is not a valid function", RlistFnCallValue(arg)->name, body->name); } arg_type = fn->dtype; } break; default: FatalError(ctx, "Cannot derive data type from Rval type %c", arg->val.type); } switch (arg->val.type) { case RVAL_TYPE_SCALAR: { const char *lval = RlistScalarValue(param); VarRef *ref = VarRefParseFromNamespaceAndScope(lval, NULL, "body", CF_NS, '.'); EvalContextVariablePut(ctx, ref, RvalScalarValue(arg->val), arg_type, "source=body"); VarRefDestroy(ref); } break; case RVAL_TYPE_LIST: { const char *lval = RlistScalarValue(param); VarRef *ref = VarRefParseFromNamespaceAndScope(lval, NULL, "body", CF_NS, '.'); EvalContextVariablePut(ctx, ref, RvalRlistValue(arg->val), arg_type, "source=body"); VarRefDestroy(ref); } break; case RVAL_TYPE_FNCALL: { FnCall *fp = RlistFnCallValue(arg); arg_type = CF_DATA_TYPE_NONE; { const FnCallType *fncall_type = FnCallTypeGet(fp->name); if (fncall_type) { arg_type = fncall_type->dtype; } } FnCallResult res = FnCallEvaluate(ctx, body->parent_policy, fp, NULL); if (res.status == FNCALL_FAILURE && THIS_AGENT_TYPE != AGENT_TYPE_COMMON) { Log(LOG_LEVEL_VERBOSE, "Embedded function argument does not resolve to a name - probably too many evaluation levels for '%s'", fp->name); } else { const char *lval = RlistScalarValue(param); void *rval = res.rval.item; VarRef *ref = VarRefParseFromNamespaceAndScope(lval, NULL, "body", CF_NS, '.'); EvalContextVariablePut(ctx, ref, rval, arg_type, "source=body"); VarRefDestroy(ref); } RvalDestroy(res.rval); } break; default: /* Nothing else should happen */ ProgrammingError("Software error: something not a scalar/function in argument literal"); } } }
static int EvalClassExpression(EvalContext *ctx, Constraint *cp, const Promise *pp) { assert(pp); int result_and = true; int result_or = false; int result_xor = 0; int result = 0, total = 0; char buffer[CF_MAXVARSIZE]; Rlist *rp; if (cp == NULL) // ProgrammingError ? We'll crash RSN anyway ... { Log(LOG_LEVEL_ERR, "EvalClassExpression internal diagnostic discovered an ill-formed condition"); } if (!IsDefinedClass(ctx, pp->classes)) { return false; } if (IsDefinedClass(ctx, pp->promiser)) { if (PromiseGetConstraintAsInt(ctx, "persistence", pp) == 0) { Log(LOG_LEVEL_VERBOSE, " ?> Cancelling cached persistent class %s", pp->promiser); EvalContextHeapPersistentRemove(pp->promiser); } return false; } switch (cp->rval.type) { Rval rval; FnCall *fp; case RVAL_TYPE_FNCALL: fp = RvalFnCallValue(cp->rval); /* Special expansion of functions for control, best effort only: */ FnCallResult res = FnCallEvaluate(ctx, PromiseGetPolicy(pp), fp, pp); FnCallDestroy(fp); cp->rval = res.rval; break; case RVAL_TYPE_LIST: for (rp = (Rlist *) cp->rval.item; rp != NULL; rp = rp->next) { rval = EvaluateFinalRval(ctx, PromiseGetPolicy(pp), NULL, "this", rp->val, true, pp); RvalDestroy(rp->val); rp->val = rval; } break; default: rval = ExpandPrivateRval(ctx, NULL, "this", cp->rval.item, cp->rval.type); RvalDestroy(cp->rval); cp->rval = rval; break; } if (strcmp(cp->lval, "expression") == 0) { return (cp->rval.type == RVAL_TYPE_SCALAR && IsDefinedClass(ctx, RvalScalarValue(cp->rval))); } if (strcmp(cp->lval, "not") == 0) { return (cp->rval.type == RVAL_TYPE_SCALAR && !IsDefinedClass(ctx, RvalScalarValue(cp->rval))); } // Class selection if (strcmp(cp->lval, "select_class") == 0) { char splay[CF_MAXVARSIZE]; int i, n; double hash; total = 0; for (rp = (Rlist *) cp->rval.item; rp != NULL; rp = rp->next) { total++; } if (total == 0) { Log(LOG_LEVEL_ERR, "No classes to select on RHS"); PromiseRef(LOG_LEVEL_ERR, pp); return false; } snprintf(splay, CF_MAXVARSIZE, "%s+%s+%ju", VFQNAME, VIPADDRESS, (uintmax_t)getuid()); hash = (double) StringHash(splay, 0, CF_HASHTABLESIZE); n = (int) (total * hash / (double) CF_HASHTABLESIZE); for (rp = (Rlist *) cp->rval.item, i = 0; rp != NULL; rp = rp->next, i++) { if (i == n) { EvalContextClassPutSoft(ctx, RlistScalarValue(rp), CONTEXT_SCOPE_NAMESPACE, "source=promise"); return true; } } } /* If we get here, anything remaining on the RHS must be a clist */ if (cp->rval.type != RVAL_TYPE_LIST) { Log(LOG_LEVEL_ERR, "RHS of promise body attribute '%s' is not a list", cp->lval); PromiseRef(LOG_LEVEL_ERR, pp); return true; } // Class distributions if (strcmp(cp->lval, "dist") == 0) { for (rp = (Rlist *) cp->rval.item; rp != NULL; rp = rp->next) { result = IntFromString(RlistScalarValue(rp)); if (result < 0) { Log(LOG_LEVEL_ERR, "Non-positive integer in class distribution"); PromiseRef(LOG_LEVEL_ERR, pp); return false; } total += result; } if (total == 0) { Log(LOG_LEVEL_ERR, "An empty distribution was specified on RHS"); PromiseRef(LOG_LEVEL_ERR, pp); return false; } double fluct = drand48(); double cum = 0.0; for (rp = (Rlist *) cp->rval.item; rp != NULL; rp = rp->next) { double prob = ((double) IntFromString(RlistScalarValue(rp))) / ((double) total); cum += prob; if (fluct < cum) { break; } } snprintf(buffer, CF_MAXVARSIZE - 1, "%s_%s", pp->promiser, RlistScalarValue(rp)); if (strcmp(PromiseGetBundle(pp)->type, "common") == 0) { EvalContextClassPutSoft(ctx, buffer, CONTEXT_SCOPE_NAMESPACE, "source=promise"); } else { EvalContextClassPutSoft(ctx, buffer, CONTEXT_SCOPE_BUNDLE, "source=promise"); } return true; } /* and/or/xor expressions */ enum { c_or = 0, c_and, c_xor } logic; if (strcmp(cp->lval, "or") == 0) { logic = c_or; } else if (strcmp(cp->lval, "and") == 0) { logic = c_and; } else if (strcmp(cp->lval, "xor") == 0) { logic = c_xor; } for (rp = (Rlist *) cp->rval.item; rp != NULL; rp = rp->next) { // tolerate unexpanded entries here and interpret as "class not set" if (rp->val.type != RVAL_TYPE_SCALAR) { result = false; } else { result = IsDefinedClass(ctx, RlistScalarValue(rp)); } // shortcut and and or switch (logic) { case c_or: if (result) { return true; } break; case c_and: if (!result) { return false; } break; default: break; } result_and = result_and && result; result_or = result_or || result; result_xor ^= result; } // Class combinations switch (logic) { case c_or: return result_or; case c_xor: return result_xor == 1; case c_and: return result_and; } return false; }
Rlist *NewExpArgs(EvalContext *ctx, const Policy *policy, const FnCall *fp, const FnCallType *fp_type) { // Functions with delayed evaluation will call this themselves later if (fp_type && fp_type->options & FNCALL_OPTION_DELAYED_EVALUATION) { return RlistCopy(fp->args); } const FnCallType *fn = FnCallTypeGet(fp->name); { int len = RlistLen(fp->args); if (!(fn->options & FNCALL_OPTION_VARARG)) { if (len != FnNumArgs(fn)) { Log(LOG_LEVEL_ERR, "Arguments to function '%s' do not tally. Expected %d not %d", fp->name, FnNumArgs(fn), len); PromiseRef(LOG_LEVEL_ERR, fp->caller); exit(EXIT_FAILURE); } } } Rlist *expanded_args = NULL; for (const Rlist *rp = fp->args; rp != NULL; rp = rp->next) { Rval rval; switch (rp->val.type) { case RVAL_TYPE_FNCALL: { FnCall *subfp = RlistFnCallValue(rp); rval = FnCallEvaluate(ctx, policy, subfp, fp->caller).rval; } break; default: rval = ExpandPrivateRval(ctx, NULL, NULL, rp->val.item, rp->val.type); assert(rval.item); break; } /* Collect compound values into containers only if the function supports it. Functions without FNCALL_OPTION_COLLECTING don't collect Rlist elements. So in the policy, you call and(splitstring("a b")) and it ends up as and("a", "b"). This expansion happens once per FnCall, not for all arguments. Functions with FNCALL_OPTION_COLLECTING instead collect all the results of a FnCall into a single JSON array object. It requires functions to expect it, but it's the only reasonable way to preserve backwards compatibility for functions like and() and allow nesting of calls to functions that take and return compound data types. */ RlistAppendAllTypes(&expanded_args, rval.item, rval.type, (fn->options & FNCALL_OPTION_COLLECTING)); RvalDestroy(rval); } return expanded_args; }
/** * @brief Collects variable constraints controlling how the promise should be converged */ static ConvergeVariableOptions CollectConvergeVariableOptions(EvalContext *ctx, const Promise *pp, bool allow_redefine) { ConvergeVariableOptions opts = { 0 }; opts.should_converge = false; opts.drop_undefined = false; opts.ok_redefine = allow_redefine; opts.cp_save = NULL; if (!IsDefinedClass(ctx, pp->classes)) { return opts; } int num_values = 0; for (size_t i = 0; i < SeqLength(pp->conlist); i++) { Constraint *cp = SeqAt(pp->conlist, i); if (strcmp(cp->lval, "comment") == 0) { continue; } if (cp->rval.item == NULL) { continue; } if (strcmp(cp->lval, "ifvarclass") == 0) { switch (cp->rval.type) { case RVAL_TYPE_SCALAR: if (!IsDefinedClass(ctx, cp->rval.item)) { return opts; } break; case RVAL_TYPE_FNCALL: { bool excluded = false; /* eval it: e.g. ifvarclass => not("a_class") */ Rval res = FnCallEvaluate(ctx, PromiseGetPolicy(pp), cp->rval.item, pp).rval; /* Don't continue unless function was evaluated properly */ if (res.type != RVAL_TYPE_SCALAR) { RvalDestroy(res); return opts; } excluded = !IsDefinedClass(ctx, res.item); RvalDestroy(res); if (excluded) { return opts; } } break; default: Log(LOG_LEVEL_ERR, "Invalid ifvarclass type '%c': should be string or function", cp->rval.type); continue; } continue; } if (strcmp(cp->lval, "policy") == 0) { if (strcmp(cp->rval.item, "ifdefined") == 0) { opts.drop_undefined = true; opts.ok_redefine = false; } else if (strcmp(cp->rval.item, "constant") == 0) { opts.ok_redefine = false; } else { opts.ok_redefine |= true; } opts.ok_redefine &= allow_redefine; } else if (DataTypeFromString(cp->lval) != CF_DATA_TYPE_NONE) { num_values++; opts.cp_save = cp; } } if (opts.cp_save == NULL) { Log(LOG_LEVEL_WARNING, "Variable body for '%s' seems incomplete", pp->promiser); PromiseRef(LOG_LEVEL_INFO, pp); return opts; } if (num_values > 2) { Log(LOG_LEVEL_ERR, "Variable '%s' breaks its own promise with multiple values (code %d)", pp->promiser, num_values); PromiseRef(LOG_LEVEL_ERR, pp); return opts; } opts.should_converge = true; return opts; }
/** * Evaluate the relevant control body, and set the * relevant fields in #ctx and #config. */ static void ResolveControlBody(EvalContext *ctx, GenericAgentConfig *config, const Body *control_body) { const char *filename = control_body->source_path; assert(CFG_CONTROLBODY[COMMON_CONTROL_MAX].lval == NULL); const ConstraintSyntax *body_syntax = NULL; for (int i = 0; CONTROL_BODIES[i].constraints != NULL; i++) { body_syntax = CONTROL_BODIES[i].constraints; if (strcmp(control_body->type, CONTROL_BODIES[i].body_type) == 0) { break; } } if (body_syntax == NULL) { FatalError(ctx, "Unknown control body: %s", control_body->type); } char *scope; assert(strcmp(control_body->name, "control") == 0); xasprintf(&scope, "control_%s", control_body->type); Log(LOG_LEVEL_DEBUG, "Initiate control variable convergence for scope '%s'", scope); EvalContextStackPushBodyFrame(ctx, NULL, control_body, NULL); for (size_t i = 0; i < SeqLength(control_body->conlist); i++) { const char *lval; Rval evaluated_rval; size_t lineno; /* Use nested scope to constrain cp. */ { Constraint *cp = SeqAt(control_body->conlist, i); lval = cp->lval; lineno = cp->offset.line; if (!IsDefinedClass(ctx, cp->classes)) { continue; } if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_BUNDLESEQUENCE].lval) == 0) { evaluated_rval = ExpandPrivateRval(ctx, NULL, scope, cp->rval.item, cp->rval.type); } else { evaluated_rval = EvaluateFinalRval(ctx, control_body->parent_policy, NULL, scope, cp->rval, true, NULL); } } /* Close scope: assert we only use evaluated_rval, not cp->rval. */ VarRef *ref = VarRefParseFromScope(lval, scope); EvalContextVariableRemove(ctx, ref); DataType rval_proper_datatype = ConstraintSyntaxGetDataType(body_syntax, lval); if (evaluated_rval.type != DataTypeToRvalType(rval_proper_datatype)) { Log(LOG_LEVEL_ERR, "Attribute '%s' in %s:%zu is of wrong type, skipping", lval, filename, lineno); VarRefDestroy(ref); RvalDestroy(evaluated_rval); continue; } bool success = EvalContextVariablePut( ctx, ref, evaluated_rval.item, rval_proper_datatype, "source=promise"); if (!success) { Log(LOG_LEVEL_ERR, "Attribute '%s' in %s:%zu can't be added, skipping", lval, filename, lineno); VarRefDestroy(ref); RvalDestroy(evaluated_rval); continue; } VarRefDestroy(ref); if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_OUTPUT_PREFIX].lval) == 0) { strlcpy(VPREFIX, RvalScalarValue(evaluated_rval), sizeof(VPREFIX)); } if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_DOMAIN].lval) == 0) { strlcpy(VDOMAIN, RvalScalarValue(evaluated_rval), sizeof(VDOMAIN)); Log(LOG_LEVEL_VERBOSE, "SET domain = %s", VDOMAIN); EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_SYS, "domain"); EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_SYS, "fqhost"); snprintf(VFQNAME, CF_MAXVARSIZE, "%s.%s", VUQNAME, VDOMAIN); EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "fqhost", VFQNAME, CF_DATA_TYPE_STRING, "inventory,source=agent,attribute_name=Host name"); EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "domain", VDOMAIN, CF_DATA_TYPE_STRING, "source=agent"); EvalContextClassPutHard(ctx, VDOMAIN, "source=agent"); } if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_IGNORE_MISSING_INPUTS].lval) == 0) { Log(LOG_LEVEL_VERBOSE, "SET ignore_missing_inputs %s", RvalScalarValue(evaluated_rval)); config->ignore_missing_inputs = BooleanFromString( RvalScalarValue(evaluated_rval)); } if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_IGNORE_MISSING_BUNDLES].lval) == 0) { Log(LOG_LEVEL_VERBOSE, "SET ignore_missing_bundles %s", RvalScalarValue(evaluated_rval)); config->ignore_missing_bundles = BooleanFromString( RvalScalarValue(evaluated_rval)); } if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_CACHE_SYSTEM_FUNCTIONS].lval) == 0) { Log(LOG_LEVEL_VERBOSE, "SET cache_system_functions %s", RvalScalarValue(evaluated_rval)); bool cache_system_functions = BooleanFromString( RvalScalarValue(evaluated_rval)); EvalContextSetEvalOption(ctx, EVAL_OPTION_CACHE_SYSTEM_FUNCTIONS, cache_system_functions); } if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_PROTOCOL_VERSION].lval) == 0) { config->protocol_version = ProtocolVersionParse( RvalScalarValue(evaluated_rval)); Log(LOG_LEVEL_VERBOSE, "SET common protocol_version: %s", PROTOCOL_VERSION_STRING[config->protocol_version]); } /* Those are package_inventory and package_module common control body options */ if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_PACKAGE_INVENTORY].lval) == 0) { AddDefaultInventoryToContext(ctx, RvalRlistValue(evaluated_rval)); Log(LOG_LEVEL_VERBOSE, "SET common package_inventory list"); } if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_PACKAGE_MODULE].lval) == 0) { AddDefaultPackageModuleToContext(ctx, RvalScalarValue(evaluated_rval)); Log(LOG_LEVEL_VERBOSE, "SET common package_module: %s", RvalScalarValue(evaluated_rval)); } if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_GOALPATTERNS].lval) == 0) { /* Ignored */ } RvalDestroy(evaluated_rval); } EvalContextStackPopFrame(ctx); free(scope); }
void VerifyVarPromise(EvalContext *ctx, const Promise *pp, bool allow_duplicates) { ConvergeVariableOptions opts = CollectConvergeVariableOptions(ctx, pp, allow_duplicates); if (!opts.should_converge) { return; } char *scope = NULL; if (strcmp("meta", pp->parent_promise_type->name) == 0) { scope = StringConcatenate(2, PromiseGetBundle(pp)->name, "_meta"); } else { scope = xstrdup(PromiseGetBundle(pp)->name); } //More consideration needs to be given to using these //a.transaction = GetTransactionConstraints(pp); Attributes a = { {0} }; a.classes = GetClassDefinitionConstraints(ctx, pp); Rval existing_var_rval; DataType existing_var_type = DATA_TYPE_NONE; EvalContextVariableGet(ctx, (VarRef) { NULL, scope, pp->promiser }, &existing_var_rval, &existing_var_type); Buffer *qualified_scope = BufferNew(); int result = 0; if (strcmp(PromiseGetNamespace(pp), "default") == 0) { result = BufferSet(qualified_scope, scope, strlen(scope)); if (result < 0) { /* * Even though there will be no problems with memory allocation, there * might be other problems. */ UnexpectedError("Problems writing to buffer"); free(scope); BufferDestroy(&qualified_scope); return; } } else { if (strchr(scope, ':') == NULL) { result = BufferPrintf(qualified_scope, "%s:%s", PromiseGetNamespace(pp), scope); if (result < 0) { /* * Even though there will be no problems with memory allocation, there * might be other problems. */ UnexpectedError("Problems writing to buffer"); free(scope); BufferDestroy(&qualified_scope); return; } } else { result = BufferSet(qualified_scope, scope, strlen(scope)); if (result < 0) { /* * Even though there will be no problems with memory allocation, there * might be other problems. */ UnexpectedError("Problems writing to buffer"); free(scope); BufferDestroy(&qualified_scope); return; } } } PromiseResult promise_result; Rval rval = opts.cp_save->rval; if (rval.item != NULL) { FnCall *fp = (FnCall *) rval.item; if (opts.cp_save->rval.type == RVAL_TYPE_FNCALL) { if (existing_var_type != DATA_TYPE_NONE) { // Already did this free(scope); BufferDestroy(&qualified_scope); return; } FnCallResult res = FnCallEvaluate(ctx, fp, pp); if (res.status == FNCALL_FAILURE) { /* We do not assign variables to failed fn calls */ RvalDestroy(res.rval); free(scope); BufferDestroy(&qualified_scope); return; } else { rval = res.rval; } } else { Buffer *conv = BufferNew(); if (strcmp(opts.cp_save->lval, "int") == 0) { result = BufferPrintf(conv, "%ld", IntFromString(opts.cp_save->rval.item)); if (result < 0) { /* * Even though there will be no problems with memory allocation, there * might be other problems. */ UnexpectedError("Problems writing to buffer"); free(scope); BufferDestroy(&qualified_scope); BufferDestroy(&conv); return; } rval = RvalCopy((Rval) {(char *)BufferData(conv), opts.cp_save->rval.type}); } else if (strcmp(opts.cp_save->lval, "real") == 0) { double real_value = 0.0; if (DoubleFromString(opts.cp_save->rval.item, &real_value)) { result = BufferPrintf(conv, "%lf", real_value); } else { result = BufferPrintf(conv, "(double conversion error)"); } if (result < 0) { /* * Even though there will be no problems with memory allocation, there * might be other problems. */ UnexpectedError("Problems writing to buffer"); free(scope); BufferDestroy(&conv); BufferDestroy(&qualified_scope); return; } rval = RvalCopy((Rval) {(char *)BufferData(conv), opts.cp_save->rval.type}); } else { rval = RvalCopy(opts.cp_save->rval); } if (rval.type == RVAL_TYPE_LIST) { Rlist *rval_list = RvalRlistValue(rval); RlistFlatten(ctx, &rval_list); rval.item = rval_list; } BufferDestroy(&conv); } if (Epimenides(ctx, PromiseGetBundle(pp)->name, pp->promiser, rval, 0)) { Log(LOG_LEVEL_ERR, "Variable \"%s\" contains itself indirectly - an unkeepable promise", pp->promiser); exit(1); } else { /* See if the variable needs recursively expanding again */ Rval returnval = EvaluateFinalRval(ctx, BufferData(qualified_scope), rval, true, pp); RvalDestroy(rval); // freed before function exit rval = returnval; } if (existing_var_type != DATA_TYPE_NONE) { if (opts.ok_redefine) /* only on second iteration, else we ignore broken promises */ { ScopeDeleteVariable(BufferData(qualified_scope), pp->promiser); } else if ((THIS_AGENT_TYPE == AGENT_TYPE_COMMON) && (CompareRval(existing_var_rval, rval) == false)) { switch (rval.type) { case RVAL_TYPE_SCALAR: Log(LOG_LEVEL_VERBOSE, "Redefinition of a constant scalar \"%s\" (was %s now %s)", pp->promiser, RvalScalarValue(existing_var_rval), RvalScalarValue(rval)); PromiseRef(LOG_LEVEL_VERBOSE, pp); break; case RVAL_TYPE_LIST: { Log(LOG_LEVEL_VERBOSE, "Redefinition of a constant list \"%s\".", pp->promiser); Writer *w = StringWriter(); RlistWrite(w, existing_var_rval.item); char *oldstr = StringWriterClose(w); Log(LOG_LEVEL_VERBOSE, "Old value: %s", oldstr); free(oldstr); w = StringWriter(); RlistWrite(w, rval.item); char *newstr = StringWriterClose(w); Log(LOG_LEVEL_VERBOSE, " New value: %s", newstr); free(newstr); PromiseRef(LOG_LEVEL_VERBOSE, pp); } break; default: break; } } } if (IsCf3VarString(pp->promiser)) { // Unexpanded variables, we don't do anything with RvalDestroy(rval); free(scope); BufferDestroy(&qualified_scope); return; } if (!FullTextMatch("[a-zA-Z0-9_\200-\377.]+(\\[.+\\])*", pp->promiser)) { Log(LOG_LEVEL_ERR, "Variable identifier contains illegal characters"); PromiseRef(LOG_LEVEL_ERR, pp); RvalDestroy(rval); free(scope); BufferDestroy(&qualified_scope); return; } if (opts.drop_undefined && rval.type == RVAL_TYPE_LIST) { for (Rlist *rp = rval.item; rp != NULL; rp = rp->next) { if (IsNakedVar(rp->item, '@')) { free(rp->item); rp->item = xstrdup(CF_NULL_VALUE); } } } if (!EvalContextVariablePut(ctx, (VarRef) { NULL, BufferData(qualified_scope), pp->promiser }, rval, DataTypeFromString(opts.cp_save->lval))) { Log(LOG_LEVEL_VERBOSE, "Unable to converge %s.%s value (possibly empty or infinite regression)", BufferData(qualified_scope), pp->promiser); PromiseRef(LOG_LEVEL_VERBOSE, pp); promise_result = PROMISE_RESULT_FAIL; } else { promise_result = PROMISE_RESULT_CHANGE; } } else { Log(LOG_LEVEL_ERR, "Variable %s has no promised value", pp->promiser); Log(LOG_LEVEL_ERR, "Rule from %s at/before line %zu", PromiseGetBundle(pp)->source_path, opts.cp_save->offset.line); promise_result = PROMISE_RESULT_FAIL; } /* * FIXME: Variable promise are exempt from normal evaluation logic still, so * they are not pushed to evaluation stack before being evaluated. Due to * this reason, we cannot call cfPS here to set classes, as it will error * out with ProgrammingError. * * In order to support 'classes' body for variables as well, we call * ClassAuditLog explicitly. */ ClassAuditLog(ctx, pp, a, promise_result); free(scope); BufferDestroy(&qualified_scope); RvalDestroy(rval); }
static bool EvalClassExpression(EvalContext *ctx, Constraint *cp, const Promise *pp) { assert(pp); if (cp == NULL) // ProgrammingError ? We'll crash RSN anyway ... { Log(LOG_LEVEL_ERR, "EvalClassExpression internal diagnostic discovered an ill-formed condition"); } if (!IsDefinedClass(ctx, pp->classes)) { return false; } if (IsDefinedClass(ctx, pp->promiser)) { if (PromiseGetConstraintAsInt(ctx, "persistence", pp) == 0) { Log(LOG_LEVEL_VERBOSE, " ?> Cancelling cached persistent class %s", pp->promiser); EvalContextHeapPersistentRemove(pp->promiser); } return false; } switch (cp->rval.type) { Rval rval; FnCall *fp; case RVAL_TYPE_FNCALL: fp = RvalFnCallValue(cp->rval); /* Special expansion of functions for control, best effort only: */ FnCallResult res = FnCallEvaluate(ctx, PromiseGetPolicy(pp), fp, pp); FnCallDestroy(fp); cp->rval = res.rval; break; case RVAL_TYPE_LIST: for (Rlist *rp = cp->rval.item; rp != NULL; rp = rp->next) { rval = EvaluateFinalRval(ctx, PromiseGetPolicy(pp), NULL, "this", rp->val, true, pp); RvalDestroy(rp->val); rp->val = rval; } break; default: rval = ExpandPrivateRval(ctx, NULL, "this", cp->rval.item, cp->rval.type); RvalDestroy(cp->rval); cp->rval = rval; break; } if (strcmp(cp->lval, "expression") == 0) { return (cp->rval.type == RVAL_TYPE_SCALAR && IsDefinedClass(ctx, RvalScalarValue(cp->rval))); } if (strcmp(cp->lval, "not") == 0) { return (cp->rval.type == RVAL_TYPE_SCALAR && !IsDefinedClass(ctx, RvalScalarValue(cp->rval))); } /* If we get here, anything remaining on the RHS must be a clist */ if (cp->rval.type != RVAL_TYPE_LIST) { Log(LOG_LEVEL_ERR, "RHS of promise body attribute '%s' is not a list", cp->lval); PromiseRef(LOG_LEVEL_ERR, pp); return true; } // Class selection if (strcmp(cp->lval, "select_class") == 0) { return SelectClass(ctx, cp->rval.item, pp); } // Class distributions if (strcmp(cp->lval, "dist") == 0) { return DistributeClass(ctx, cp->rval.item, pp); } /* Combine with and/or/xor: */ if (strcmp(cp->lval, "or") == 0) { return EvalBoolCombination(ctx, cp->rval.item, c_or); } else if (strcmp(cp->lval, "and") == 0) { return EvalBoolCombination(ctx, cp->rval.item, c_and); } else if (strcmp(cp->lval, "xor") == 0) { return EvalBoolCombination(ctx, cp->rval.item, c_xor); } return false; }
void ScopeAugment(EvalContext *ctx, const Bundle *bp, const Promise *pp, const Rlist *arguments) { if (RlistLen(bp->args) != RlistLen(arguments)) { Log(LOG_LEVEL_ERR, "While constructing scope '%s'", bp->name); fprintf(stderr, "Formal = "); RlistShow(stderr, bp->args); fprintf(stderr, ", Actual = "); RlistShow(stderr, arguments); fprintf(stderr, "\n"); FatalError(ctx, "Augment scope, formal and actual parameter mismatch is fatal"); } const Bundle *pbp = NULL; if (pp != NULL) { pbp = PromiseGetBundle(pp); } for (const Rlist *rpl = bp->args, *rpr = arguments; rpl != NULL; rpl = rpl->next, rpr = rpr->next) { const char *lval = rpl->item; Log(LOG_LEVEL_VERBOSE, "Augment scope '%s' with variable '%s' (type: %c)", bp->name, lval, rpr->type); // CheckBundleParameters() already checked that there is no namespace collision // By this stage all functions should have been expanded, so we only have scalars left if (IsNakedVar(rpr->item, '@')) { DataType vtype; char naked[CF_BUFSIZE]; GetNaked(naked, rpr->item); Rval retval; if (pbp != NULL) { EvalContextVariableGet(ctx, (VarRef) { pbp->ns, pbp->name, naked }, &retval, &vtype); } else { EvalContextVariableGet(ctx, (VarRef) { NULL, bp->name, naked }, &retval, &vtype); } switch (vtype) { case DATA_TYPE_STRING_LIST: case DATA_TYPE_INT_LIST: case DATA_TYPE_REAL_LIST: EvalContextVariablePut(ctx, (VarRef) { NULL, bp->name, lval }, (Rval) { retval.item, RVAL_TYPE_LIST}, DATA_TYPE_STRING_LIST); break; default: Log(LOG_LEVEL_ERR, "List parameter '%s' not found while constructing scope '%s' - use @(scope.variable) in calling reference", naked, bp->name); EvalContextVariablePut(ctx, (VarRef) { NULL, bp->name, lval }, (Rval) { rpr->item, RVAL_TYPE_SCALAR }, DATA_TYPE_STRING); break; } } else { switch(rpr->type) { case RVAL_TYPE_SCALAR: EvalContextVariablePut(ctx, (VarRef) { NULL, bp->name, lval }, (Rval) { rpr->item, RVAL_TYPE_SCALAR }, DATA_TYPE_STRING); break; case RVAL_TYPE_FNCALL: { FnCall *subfp = rpr->item; Rval rval = FnCallEvaluate(ctx, subfp, pp).rval; if (rval.type == RVAL_TYPE_SCALAR) { EvalContextVariablePut(ctx, (VarRef) { NULL, bp->name, lval }, (Rval) { rval.item, RVAL_TYPE_SCALAR }, DATA_TYPE_STRING); } else { Log(LOG_LEVEL_ERR, "Only functions returning scalars can be used as arguments"); } } break; default: ProgrammingError("An argument neither a scalar nor a list seemed to appear. Impossible"); } } } /* Check that there are no danglers left to evaluate in the hash table itself */ { Scope *ptr = ScopeGet(bp->name); AssocHashTableIterator i = HashIteratorInit(ptr->hashtable); CfAssoc *assoc = NULL; while ((assoc = HashIteratorNext(&i))) { Rval retval = ExpandPrivateRval(ctx, bp->name, assoc->rval); // Retain the assoc, just replace rval RvalDestroy(assoc->rval); assoc->rval = retval; } } return; }
PromiseResult VerifyVarPromise(EvalContext *ctx, const Promise *pp, bool allow_duplicates) { ConvergeVariableOptions opts = CollectConvergeVariableOptions(ctx, pp, allow_duplicates); if (!opts.should_converge) { return PROMISE_RESULT_NOOP; } Attributes a = { {0} }; // More consideration needs to be given to using these //a.transaction = GetTransactionConstraints(pp); a.classes = GetClassDefinitionConstraints(ctx, pp); VarRef *ref = VarRefParseFromBundle(pp->promiser, PromiseGetBundle(pp)); if (strcmp("meta", pp->parent_promise_type->name) == 0) { VarRefSetMeta(ref, true); } DataType existing_value_type = CF_DATA_TYPE_NONE; const void *const existing_value = IsExpandable(pp->promiser) ? NULL : EvalContextVariableGet(ctx, ref, &existing_value_type); PromiseResult result = PROMISE_RESULT_NOOP; Rval rval = opts.cp_save->rval; if (rval.item != NULL) { DataType data_type = DataTypeFromString(opts.cp_save->lval); if (opts.cp_save->rval.type == RVAL_TYPE_FNCALL) { FnCall *fp = RvalFnCallValue(rval); const FnCallType *fn = FnCallTypeGet(fp->name); if (!fn) { assert(false && "Canary: should have been caught before this point"); FatalError(ctx, "While setting variable '%s' in bundle '%s', unknown function '%s'", pp->promiser, PromiseGetBundle(pp)->name, fp->name); } if (fn->dtype != DataTypeFromString(opts.cp_save->lval)) { FatalError(ctx, "While setting variable '%s' in bundle '%s', variable declared type '%s' but function '%s' returns type '%s'", pp->promiser, PromiseGetBundle(pp)->name, opts.cp_save->lval, fp->name, DataTypeToString(fn->dtype)); } if (existing_value_type != CF_DATA_TYPE_NONE) { // Already did this VarRefDestroy(ref); return PROMISE_RESULT_NOOP; } FnCallResult res = FnCallEvaluate(ctx, PromiseGetPolicy(pp), fp, pp); if (res.status == FNCALL_FAILURE) { /* We do not assign variables to failed fn calls */ RvalDestroy(res.rval); VarRefDestroy(ref); return PROMISE_RESULT_NOOP; } else { rval = res.rval; } } else { Buffer *conv = BufferNew(); bool malformed = false, misprint = false; if (strcmp(opts.cp_save->lval, "int") == 0) { long int asint = IntFromString(opts.cp_save->rval.item); if (asint == CF_NOINT) { malformed = true; } else if (0 > BufferPrintf(conv, "%ld", asint)) { misprint = true; } else { rval = RvalNew(BufferData(conv), opts.cp_save->rval.type); } } else if (strcmp(opts.cp_save->lval, "real") == 0) { double real_value; if (!DoubleFromString(opts.cp_save->rval.item, &real_value)) { malformed = true; } else if (0 > BufferPrintf(conv, "%lf", real_value)) { misprint = true; } else { rval = RvalNew(BufferData(conv), opts.cp_save->rval.type); } } else { rval = RvalCopy(opts.cp_save->rval); } BufferDestroy(conv); if (malformed) { /* Arises when opts->cp_save->rval.item isn't yet expanded. */ /* Has already been logged by *FromString */ VarRefDestroy(ref); return PromiseResultUpdate(result, PROMISE_RESULT_FAIL); } else if (misprint) { /* Even though no problems with memory allocation can * get here, there might be other problems. */ UnexpectedError("Problems writing to buffer"); VarRefDestroy(ref); return PROMISE_RESULT_NOOP; } else if (rval.type == RVAL_TYPE_LIST) { Rlist *rval_list = RvalRlistValue(rval); RlistFlatten(ctx, &rval_list); rval.item = rval_list; } } if (Epimenides(ctx, PromiseGetBundle(pp)->ns, PromiseGetBundle(pp)->name, pp->promiser, rval, 0)) { Log(LOG_LEVEL_ERR, "Variable '%s' contains itself indirectly - an unkeepable promise", pp->promiser); exit(EXIT_FAILURE); } else { /* See if the variable needs recursively expanding again */ Rval returnval = EvaluateFinalRval(ctx, PromiseGetPolicy(pp), ref->ns, ref->scope, rval, true, pp); RvalDestroy(rval); // freed before function exit rval = returnval; } if (existing_value_type != CF_DATA_TYPE_NONE) { if (!opts.ok_redefine) /* only on second iteration, else we ignore broken promises */ { if (THIS_AGENT_TYPE == AGENT_TYPE_COMMON && !CompareRval(existing_value, DataTypeToRvalType(existing_value_type), rval.item, rval.type)) { switch (rval.type) { case RVAL_TYPE_SCALAR: Log(LOG_LEVEL_VERBOSE, "Redefinition of a constant scalar '%s', was '%s' now '%s'", pp->promiser, (const char *)existing_value, RvalScalarValue(rval)); PromiseRef(LOG_LEVEL_VERBOSE, pp); break; case RVAL_TYPE_LIST: { Log(LOG_LEVEL_VERBOSE, "Redefinition of a constant list '%s'", pp->promiser); Writer *w = StringWriter(); RlistWrite(w, existing_value); char *oldstr = StringWriterClose(w); Log(LOG_LEVEL_VERBOSE, "Old value '%s'", oldstr); free(oldstr); w = StringWriter(); RlistWrite(w, rval.item); char *newstr = StringWriterClose(w); Log(LOG_LEVEL_VERBOSE, " New value '%s'", newstr); free(newstr); PromiseRef(LOG_LEVEL_VERBOSE, pp); } break; case RVAL_TYPE_CONTAINER: case RVAL_TYPE_FNCALL: case RVAL_TYPE_NOPROMISEE: break; } } RvalDestroy(rval); VarRefDestroy(ref); return result; } } if (IsCf3VarString(pp->promiser)) { // Unexpanded variables, we don't do anything with RvalDestroy(rval); VarRefDestroy(ref); return result; } if (!IsValidVariableName(pp->promiser)) { Log(LOG_LEVEL_ERR, "Variable identifier contains illegal characters"); PromiseRef(LOG_LEVEL_ERR, pp); RvalDestroy(rval); VarRefDestroy(ref); return result; } if (rval.type == RVAL_TYPE_LIST) { if (opts.drop_undefined) { for (Rlist *rp = RvalRlistValue(rval); rp; rp = rp->next) { if (IsNakedVar(RlistScalarValue(rp), '@')) { free(rp->val.item); rp->val.item = xstrdup(CF_NULL_VALUE); } } } for (const Rlist *rp = RvalRlistValue(rval); rp; rp = rp->next) { switch (rp->val.type) { case RVAL_TYPE_SCALAR: break; default: // Cannot assign variable because value is a list containing a non-scalar item VarRefDestroy(ref); RvalDestroy(rval); return result; } } } if (ref->num_indices > 0) { if (data_type == CF_DATA_TYPE_CONTAINER) { char *lval_str = VarRefToString(ref, true); Log(LOG_LEVEL_ERR, "Cannot assign a container to an indexed variable name '%s'. Should be assigned to '%s' instead", lval_str, ref->lval); free(lval_str); VarRefDestroy(ref); RvalDestroy(rval); return result; } else { DataType existing_type = CF_DATA_TYPE_NONE; VarRef *base_ref = VarRefCopyIndexless(ref); if (EvalContextVariableGet(ctx, ref, &existing_type) && existing_type == CF_DATA_TYPE_CONTAINER) { char *lval_str = VarRefToString(ref, true); char *base_ref_str = VarRefToString(base_ref, true); Log(LOG_LEVEL_ERR, "Cannot assign value to indexed variable name '%s', because a container is already assigned to the base name '%s'", lval_str, base_ref_str); free(lval_str); free(base_ref_str); VarRefDestroy(base_ref); VarRefDestroy(ref); RvalDestroy(rval); return result; } VarRefDestroy(base_ref); } } DataType required_datatype = DataTypeFromString(opts.cp_save->lval); if (rval.type != DataTypeToRvalType(required_datatype)) { char *ref_str = VarRefToString(ref, true); char *value_str = RvalToString(rval); Log(LOG_LEVEL_ERR, "Variable '%s' expected a variable of type '%s', but was given incompatible value '%s'", ref_str, DataTypeToString(required_datatype), value_str); PromiseRef(LOG_LEVEL_ERR, pp); free(ref_str); free(value_str); VarRefDestroy(ref); RvalDestroy(rval); return PromiseResultUpdate(result, PROMISE_RESULT_FAIL); } if (!EvalContextVariablePut(ctx, ref, rval.item, required_datatype, "source=promise")) { Log(LOG_LEVEL_VERBOSE, "Unable to converge %s.%s value (possibly empty or infinite regression)", ref->scope, pp->promiser); PromiseRef(LOG_LEVEL_VERBOSE, pp); result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL); } else { Rlist *promise_meta = PromiseGetConstraintAsList(ctx, "meta", pp); if (promise_meta) { StringSet *class_meta = EvalContextVariableTags(ctx, ref); Buffer *print; for (const Rlist *rp = promise_meta; rp; rp = rp->next) { StringSetAdd(class_meta, xstrdup(RlistScalarValue(rp))); print = StringSetToBuffer(class_meta, ','); Log(LOG_LEVEL_DEBUG, "Added tag %s to class %s, tags now [%s]", RlistScalarValue(rp), pp->promiser, BufferData(print)); BufferDestroy(print); } } } } else { Log(LOG_LEVEL_ERR, "Variable %s has no promised value", pp->promiser); Log(LOG_LEVEL_ERR, "Rule from %s at/before line %llu", PromiseGetBundle(pp)->source_path, (unsigned long long)opts.cp_save->offset.line); result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL); } /* * FIXME: Variable promise are exempt from normal evaluation logic still, so * they are not pushed to evaluation stack before being evaluated. Due to * this reason, we cannot call cfPS here to set classes, as it will error * out with ProgrammingError. * * In order to support 'classes' body for variables as well, we call * ClassAuditLog explicitly. */ ClassAuditLog(ctx, pp, a, result); VarRefDestroy(ref); RvalDestroy(rval); return result; }
void ScopeAugment(EvalContext *ctx, const Bundle *bp, const Promise *pp, const Rlist *arguments) { if (RlistLen(bp->args) != RlistLen(arguments)) { Log(LOG_LEVEL_ERR, "While constructing scope '%s'", bp->name); fprintf(stderr, "Formal = "); RlistShow(stderr, bp->args); fprintf(stderr, ", Actual = "); RlistShow(stderr, arguments); fprintf(stderr, "\n"); FatalError(ctx, "Augment scope, formal and actual parameter mismatch is fatal"); } const Bundle *pbp = NULL; if (pp != NULL) { pbp = PromiseGetBundle(pp); } for (const Rlist *rpl = bp->args, *rpr = arguments; rpl != NULL; rpl = rpl->next, rpr = rpr->next) { const char *lval = RlistScalarValue(rpl); Log(LOG_LEVEL_VERBOSE, "Augment scope '%s' with variable '%s' (type: %c)", bp->name, lval, rpr->val.type); // CheckBundleParameters() already checked that there is no namespace collision // By this stage all functions should have been expanded, so we only have scalars left if (rpr->val.type == RVAL_TYPE_SCALAR && IsNakedVar(RlistScalarValue(rpr), '@')) { char naked[CF_BUFSIZE]; GetNaked(naked, RlistScalarValue(rpr)); DataType value_type = CF_DATA_TYPE_NONE; const void *value = NULL; if (pbp != NULL) { VarRef *ref = VarRefParseFromBundle(naked, pbp); value = EvalContextVariableGet(ctx, ref, &value_type); VarRefDestroy(ref); } else { VarRef *ref = VarRefParseFromBundle(naked, bp); value = EvalContextVariableGet(ctx, ref, &value_type); VarRefDestroy(ref); } switch (value_type) { case CF_DATA_TYPE_STRING_LIST: case CF_DATA_TYPE_INT_LIST: case CF_DATA_TYPE_REAL_LIST: { VarRef *ref = VarRefParseFromBundle(lval, bp); EvalContextVariablePut(ctx, ref, value, CF_DATA_TYPE_STRING_LIST, "source=promise"); VarRefDestroy(ref); } break; case CF_DATA_TYPE_CONTAINER: { VarRef *ref = VarRefParseFromBundle(lval, bp); EvalContextVariablePut(ctx, ref, value, CF_DATA_TYPE_CONTAINER, "source=promise"); VarRefDestroy(ref); } break; default: { Log(LOG_LEVEL_ERR, "List or container parameter '%s' not found while constructing scope '%s' - use @(scope.variable) in calling reference", naked, bp->name); VarRef *ref = VarRefParseFromBundle(lval, bp); EvalContextVariablePut(ctx, ref, RlistScalarValue(rpr), CF_DATA_TYPE_STRING, "source=promise"); VarRefDestroy(ref); } break; } } else { switch(rpr->val.type) { case RVAL_TYPE_SCALAR: { VarRef *ref = VarRefParseFromBundle(lval, bp); EvalContextVariablePut(ctx, ref, RvalScalarValue(rpr->val), CF_DATA_TYPE_STRING, "source=promise"); VarRefDestroy(ref); } break; case RVAL_TYPE_FNCALL: { FnCall *subfp = RlistFnCallValue(rpr); Rval rval = FnCallEvaluate(ctx, PromiseGetPolicy(pp), subfp, pp).rval; if (rval.type == RVAL_TYPE_SCALAR) { VarRef *ref = VarRefParseFromBundle(lval, bp); EvalContextVariablePut(ctx, ref, RvalScalarValue(rval), CF_DATA_TYPE_STRING, "source=promise"); VarRefDestroy(ref); } else { Log(LOG_LEVEL_ERR, "Only functions returning scalars can be used as arguments"); } RvalDestroy(rval); } break; default: ProgrammingError("An argument neither a scalar nor a list seemed to appear. Impossible"); } } } /* Check that there are no danglers left to evaluate in the hash table itself */ return; }
static int EvalClassExpression(EvalContext *ctx, Constraint *cp, Promise *pp) { int result_and = true; int result_or = false; int result_xor = 0; int result = 0, total = 0; char buffer[CF_MAXVARSIZE]; Rlist *rp; FnCall *fp; Rval rval; if (cp == NULL) { Log(LOG_LEVEL_ERR, "EvalClassExpression internal diagnostic discovered an ill-formed condition"); } if (!IsDefinedClass(ctx, pp->classes, PromiseGetNamespace(pp))) { return false; } if (EvalContextPromiseIsDone(ctx, pp)) { return false; } if (IsDefinedClass(ctx, pp->promiser, PromiseGetNamespace(pp))) { if (PromiseGetConstraintAsInt(ctx, "persistence", pp) == 0) { Log(LOG_LEVEL_VERBOSE, " ?> Cancelling cached persistent class %s", pp->promiser); EvalContextHeapPersistentRemove(pp->promiser); } return false; } switch (cp->rval.type) { case RVAL_TYPE_FNCALL: fp = (FnCall *) cp->rval.item; /* Special expansion of functions for control, best effort only */ FnCallResult res = FnCallEvaluate(ctx, fp, pp); FnCallDestroy(fp); cp->rval = res.rval; break; case RVAL_TYPE_LIST: for (rp = (Rlist *) cp->rval.item; rp != NULL; rp = rp->next) { rval = EvaluateFinalRval(ctx, "this", (Rval) {rp->item, rp->type}, true, pp); RvalDestroy((Rval) {rp->item, rp->type}); rp->item = rval.item; rp->type = rval.type; } break; default: rval = ExpandPrivateRval(ctx, "this", cp->rval); RvalDestroy(cp->rval); cp->rval = rval; break; } if (strcmp(cp->lval, "expression") == 0) { if (cp->rval.type != RVAL_TYPE_SCALAR) { return false; } if (IsDefinedClass(ctx, (char *) cp->rval.item, PromiseGetNamespace(pp))) { return true; } else { return false; } } if (strcmp(cp->lval, "not") == 0) { if (cp->rval.type != RVAL_TYPE_SCALAR) { return false; } if (IsDefinedClass(ctx, (char *) cp->rval.item, PromiseGetNamespace(pp))) { return false; } else { return true; } } // Class selection if (strcmp(cp->lval, "select_class") == 0) { char splay[CF_MAXVARSIZE]; int i, n; double hash; total = 0; for (rp = (Rlist *) cp->rval.item; rp != NULL; rp = rp->next) { total++; } if (total == 0) { Log(LOG_LEVEL_ERR, "No classes to select on RHS"); PromiseRef(LOG_LEVEL_ERR, pp); return false; } snprintf(splay, CF_MAXVARSIZE, "%s+%s+%ju", VFQNAME, VIPADDRESS, (uintmax_t)getuid()); hash = (double) OatHash(splay, CF_HASHTABLESIZE); n = (int) (total * hash / (double) CF_HASHTABLESIZE); for (rp = (Rlist *) cp->rval.item, i = 0; rp != NULL; rp = rp->next, i++) { if (i == n) { EvalContextHeapAddSoft(ctx, rp->item, PromiseGetNamespace(pp)); return true; } } } /* If we get here, anything remaining on the RHS must be a clist */ if (cp->rval.type != RVAL_TYPE_LIST) { Log(LOG_LEVEL_ERR, "RHS of promise body attribute '%s' is not a list", cp->lval); PromiseRef(LOG_LEVEL_ERR, pp); return true; } // Class distributions if (strcmp(cp->lval, "dist") == 0) { for (rp = (Rlist *) cp->rval.item; rp != NULL; rp = rp->next) { result = IntFromString(rp->item); if (result < 0) { Log(LOG_LEVEL_ERR, "Non-positive integer in class distribution"); PromiseRef(LOG_LEVEL_ERR, pp); return false; } total += result; } if (total == 0) { Log(LOG_LEVEL_ERR, "An empty distribution was specified on RHS"); PromiseRef(LOG_LEVEL_ERR, pp); return false; } double fluct = drand48(); double cum = 0.0; for (rp = (Rlist *) cp->rval.item; rp != NULL; rp = rp->next) { double prob = ((double) IntFromString(rp->item)) / ((double) total); cum += prob; if (fluct < cum) { break; } } snprintf(buffer, CF_MAXVARSIZE - 1, "%s_%s", pp->promiser, (char *) rp->item); /* FIXME: figure why explicit mark and get rid of it */ EvalContextMarkPromiseDone(ctx, pp); if (strcmp(PromiseGetBundle(pp)->type, "common") == 0) { EvalContextHeapAddSoft(ctx, buffer, PromiseGetNamespace(pp)); } else { EvalContextStackFrameAddSoft(ctx, buffer); } return true; } /* and/or/xor expressions */ for (rp = (Rlist *) cp->rval.item; rp != NULL; rp = rp->next) { if (rp->type != RVAL_TYPE_SCALAR) { return false; } result = IsDefinedClass(ctx, (char *) (rp->item), PromiseGetNamespace(pp)); result_and = result_and && result; result_or = result_or || result; result_xor ^= result; } // Class combinations if (strcmp(cp->lval, "or") == 0) { return result_or; } if (strcmp(cp->lval, "xor") == 0) { return (result_xor == 1) ? true : false; } if (strcmp(cp->lval, "and") == 0) { return result_and; } return false; }
/** * @brief Collects variable constraints controlling how the promise should be converged */ static ConvergeVariableOptions CollectConvergeVariableOptions(EvalContext *ctx, const Promise *pp, bool allow_redefine) { ConvergeVariableOptions opts = { 0 }; opts.should_converge = false; opts.drop_undefined = false; opts.ok_redefine = allow_redefine; opts.cp_save = NULL; if (EvalContextPromiseIsDone(ctx, pp)) { return opts; } if (!IsDefinedClass(ctx, pp->classes, PromiseGetNamespace(pp))) { return opts; } int num_values = 0; for (size_t i = 0; i < SeqLength(pp->conlist); i++) { Constraint *cp = SeqAt(pp->conlist, i); if (strcmp(cp->lval, "comment") == 0) { continue; } if (cp->rval.item == NULL) { continue; } if (strcmp(cp->lval, "ifvarclass") == 0) { Rval res; switch (cp->rval.type) { case RVAL_TYPE_SCALAR: if (!IsDefinedClass(ctx, cp->rval.item, PromiseGetNamespace(pp))) { return opts; } break; case RVAL_TYPE_FNCALL: { bool excluded = false; /* eval it: e.g. ifvarclass => not("a_class") */ res = FnCallEvaluate(ctx, cp->rval.item, NULL).rval; /* Don't continue unless function was evaluated properly */ if (res.type != RVAL_TYPE_SCALAR) { RvalDestroy(res); return opts; } excluded = !IsDefinedClass(ctx, res.item, PromiseGetNamespace(pp)); RvalDestroy(res); if (excluded) { return opts; } } break; default: CfOut(OUTPUT_LEVEL_ERROR, "", "!! Invalid ifvarclass type '%c': should be string or function", cp->rval.type); continue; } continue; } if (strcmp(cp->lval, "policy") == 0) { if (strcmp(cp->rval.item, "ifdefined") == 0) { opts.drop_undefined = true; opts.ok_redefine = false; } else if (strcmp(cp->rval.item, "constant") == 0) { opts.ok_redefine = false; } else { opts.ok_redefine |= true; } } else if (IsDataType(cp->lval)) { num_values++; opts.cp_save = cp; } } if (opts.cp_save == NULL) { CfOut(OUTPUT_LEVEL_INFORM, "", "Warning: Variable body for \"%s\" seems incomplete", pp->promiser); PromiseRef(OUTPUT_LEVEL_INFORM, pp); return opts; } if (num_values > 2) { CfOut(OUTPUT_LEVEL_ERROR, "", "Variable \"%s\" breaks its own promise with multiple values (code %d)", pp->promiser, num_values); PromiseRef(OUTPUT_LEVEL_ERROR, pp); return opts; } opts.should_converge = true; return opts; }