Rval ExpandDanglers(EvalContext *ctx, const char *ns, const char *scope, Rval rval, const Promise *pp) { assert(ctx); assert(pp); switch (rval.type) { case RVAL_TYPE_SCALAR: if (IsCf3VarString(RvalScalarValue(rval))) { return EvaluateFinalRval(ctx, PromiseGetPolicy(pp), ns, scope, rval, false, pp); } else { return RvalCopy(rval); } break; case RVAL_TYPE_LIST: { Rlist *result_list = RlistCopy(RvalRlistValue(rval)); RlistFlatten(ctx, &result_list); return RvalNew(result_list, RVAL_TYPE_LIST); } break; case RVAL_TYPE_CONTAINER: case RVAL_TYPE_FNCALL: case RVAL_TYPE_NOPROMISEE: return RvalCopy(rval); } ProgrammingError("Unhandled Rval type"); }
Rlist *RlistAppend(Rlist **start, const void *item, RvalType type) { Rlist *lp = *start; switch (type) { case RVAL_TYPE_SCALAR: return RlistAppendScalar(start, item); case RVAL_TYPE_FNCALL: break; case RVAL_TYPE_LIST: for (const Rlist *rp = item; rp; rp = rp->next) { lp = RlistAppendRval(start, RvalCopy(rp->val)); } return lp; default: Log(LOG_LEVEL_DEBUG, "Cannot append %c to rval-list '%s'", type, (char *) item); return NULL; } Rlist *rp = xmalloc(sizeof(Rlist)); if (*start == NULL) { *start = rp; } else { for (lp = *start; lp->next != NULL; lp = lp->next) { } lp->next = rp; } rp->val = RvalCopy((Rval) {(void *) item, type}); ThreadLock(cft_lock); rp->next = NULL; ThreadUnlock(cft_lock); return rp; }
void RlistFlatten(EvalContext *ctx, Rlist **list) { for (Rlist *rp = *list; rp != NULL;) { if (rp->val.type != RVAL_TYPE_SCALAR) { rp = rp->next; continue; } char naked[CF_BUFSIZE] = ""; if (IsNakedVar(RlistScalarValue(rp), '@')) { GetNaked(naked, RlistScalarValue(rp)); if (!IsExpandable(naked)) { Rval rv; VarRef *ref = VarRefParse(naked); bool var_found = EvalContextVariableGet(ctx, ref, &rv, NULL); VarRefDestroy(ref); if (var_found) { switch (rv.type) { case RVAL_TYPE_LIST: for (const Rlist *srp = rv.item; srp != NULL; srp = srp->next) { RlistAppendRval(list, RvalCopy(srp->val)); } Rlist *next = rp->next; RlistDestroyEntry(list, rp); rp = next; continue; default: ProgrammingError("List variable does not resolve to a list"); RlistAppendRval(list, RvalCopy(rp->val)); break; } } } } rp = rp->next; } }
static PromiseResult DoVerifyServices(EvalContext *ctx, Attributes a, const Promise *pp) { Rval call; { const Constraint *cp = PromiseGetConstraint(pp, "service_bundle"); if (cp) { call = RvalCopy(cp->rval); } else { call = (Rval) { DefaultServiceBundleCall(pp, a.service.service_policy), RVAL_TYPE_FNCALL }; } } a.havebundle = true; EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "service_policy", a.service.service_policy, CF_DATA_TYPE_STRING, "source=promise"); PromiseResult result = PROMISE_RESULT_NOOP; result = PromiseResultUpdate(result, VerifyMethod(ctx, call, a, pp)); // Send list of classes to set privately? RvalDestroy(call); return result; }
Rlist *RlistAppend(Rlist **start, const void *item, RvalType type) { Rlist *rp, *lp = *start; switch (type) { case RVAL_TYPE_SCALAR: return RlistAppendScalar(start, item); case RVAL_TYPE_FNCALL: break; case RVAL_TYPE_LIST: for (rp = (Rlist *) item; rp != NULL; rp = rp->next) { lp = RlistAppend(start, rp->item, rp->type); } return lp; default: Log(LOG_LEVEL_DEBUG, "Cannot append %c to rval-list '%s'", type, (char *) item); return NULL; } rp = xmalloc(sizeof(Rlist)); if (*start == NULL) { *start = rp; } else { for (lp = *start; lp->next != NULL; lp = lp->next) { } lp->next = rp; } rp->item = RvalCopy((Rval) {(void *) item, type}).item; rp->type = type; /* scalar, builtin function */ ThreadLock(cft_lock); if (type == RVAL_TYPE_LIST) { rp->state_ptr = rp->item; } else { rp->state_ptr = NULL; } rp->next = NULL; ThreadUnlock(cft_lock); return rp; }
Rlist *RlistCopy(const Rlist *rp) { Rlist *start = NULL; while (rp != NULL) { RlistAppendRval(&start, RvalCopy(rp->val)); rp = rp->next; } return start; }
Rlist *RlistCopy(const Rlist *list) { Rlist *start = NULL; if (list == NULL) { return NULL; } for (const Rlist *rp = list; rp != NULL; rp = rp->next) { RlistAppendRval(&start, RvalCopy(rp->val)); } return start; }
Rlist *RlistPrepend(Rlist **start, const void *item, RvalType type) /* heap memory for item must have already been allocated */ { Rlist *rp, *lp = *start; switch (type) { case RVAL_TYPE_SCALAR: return RlistPrependScalar(start, item); case RVAL_TYPE_LIST: CfDebug("Expanding and prepending list (ends up in reverse)\n"); for (rp = (Rlist *) item; rp != NULL; rp = rp->next) { lp = RlistPrepend(start, rp->item, rp->type); } return lp; case RVAL_TYPE_FNCALL: return RlistPrependFnCall(start, item); default: CfDebug("Cannot prepend %c to rval-list [%s]\n", type, (char *) item); return NULL; } rp = xmalloc(sizeof(Rlist)); rp->next = *start; rp->item = RvalCopy((Rval) { (void *)item, type}).item; rp->type = type; /* scalar, builtin function */ if (type == RVAL_TYPE_LIST) { rp->state_ptr = rp->item; } else { rp->state_ptr = NULL; } ThreadLock(cft_lock); *start = rp; ThreadUnlock(cft_lock); return rp; }
static PromiseResult DoVerifyServices(EvalContext *ctx, Attributes a, const Promise *pp) { Rval call; { const Constraint *cp = PromiseGetConstraint(pp, "service_bundle"); if (cp) { call = RvalCopy(cp->rval); } else { call = (Rval) { DefaultServiceBundleCall(pp, a.service.service_policy), RVAL_TYPE_FNCALL }; } } a.havebundle = true; switch (a.service.service_policy) { case SERVICE_POLICY_START: EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "service_policy", "start", CF_DATA_TYPE_STRING, "source=promise"); break; case SERVICE_POLICY_RESTART: EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "service_policy", "restart", CF_DATA_TYPE_STRING, "source=promise"); break; case SERVICE_POLICY_RELOAD: EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "service_policy", "reload", CF_DATA_TYPE_STRING, "source=promise"); break; case SERVICE_POLICY_STOP: case SERVICE_POLICY_DISABLE: default: EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "service_policy", "stop", CF_DATA_TYPE_STRING, "source=promise"); break; } PromiseResult result = PROMISE_RESULT_NOOP; if (!DONTDO) { result = PromiseResultUpdate(result, VerifyMethod(ctx, call, a, pp)); // Send list of classes to set privately? } RvalDestroy(call); return result; }
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"); } } }
Rlist *RlistPrepend(Rlist **start, const void *item, RvalType type) { switch (type) { case RVAL_TYPE_LIST: { Rlist *lp = NULL; for (const Rlist *rp = item; rp; rp = rp->next) { lp = RlistPrependRval(start, RvalCopy(rp->val)); } return lp; } case RVAL_TYPE_SCALAR: case RVAL_TYPE_FNCALL: case RVAL_TYPE_CONTAINER: case RVAL_TYPE_NOPROMISEE: return RlistPrependRval(start, RvalNew(item, type)); } assert(false); return NULL; }
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; }
Promise *DeRefCopyPromise(EvalContext *ctx, const Promise *pp) { Promise *pcopy; Rval returnval; pcopy = xcalloc(1, sizeof(Promise)); if (pp->promiser) { pcopy->promiser = xstrdup(pp->promiser); } if (pp->promisee.item) { pcopy->promisee = RvalCopy(pp->promisee); if (pcopy->promisee.type == RVAL_TYPE_LIST) { Rlist *rval_list = RvalRlistValue(pcopy->promisee); RlistFlatten(ctx, &rval_list); pcopy->promisee.item = rval_list; } } if (pp->classes) { pcopy->classes = xstrdup(pp->classes); } /* FIXME: may it happen? */ if ((pp->promisee.item != NULL && pcopy->promisee.item == NULL)) { ProgrammingError("Unable to copy promise"); } pcopy->parent_promise_type = pp->parent_promise_type; pcopy->offset.line = pp->offset.line; pcopy->comment = pp->comment ? xstrdup(pp->comment) : NULL; pcopy->has_subbundles = pp->has_subbundles; pcopy->conlist = SeqNew(10, ConstraintDestroy); pcopy->org_pp = pp->org_pp; pcopy->offset = pp->offset; /* No further type checking should be necessary here, already done by CheckConstraintTypeMatch */ for (size_t i = 0; i < SeqLength(pp->conlist); i++) { Constraint *cp = SeqAt(pp->conlist, i); Body *bp = NULL; FnCall *fp = NULL; /* A body template reference could look like a scalar or fn to the parser w/w () */ const Policy *policy = PolicyFromPromise(pp); Seq *bodies = policy ? policy->bodies : NULL; char body_ns[CF_MAXVARSIZE] = ""; char body_name[CF_MAXVARSIZE] = ""; switch (cp->rval.type) { case RVAL_TYPE_SCALAR: if (cp->references_body) { SplitScopeName(RvalScalarValue(cp->rval), body_ns, body_name); if (EmptyString(body_ns)) { strncpy(body_ns, PromiseGetNamespace(pp), CF_MAXVARSIZE); } bp = IsBody(bodies, body_ns, body_name); } fp = NULL; break; case RVAL_TYPE_FNCALL: fp = RvalFnCallValue(cp->rval); SplitScopeName(fp->name, body_ns, body_name); if (EmptyString(body_ns)) { strncpy(body_ns, PromiseGetNamespace(pp), CF_MAXVARSIZE); } bp = IsBody(bodies, body_ns, body_name); break; default: bp = NULL; fp = NULL; break; } /* First case is: we have a body template to expand lval = body(args), .. */ if (bp) { EvalContextStackPushBodyFrame(ctx, pcopy, bp, fp ? fp->args : NULL); if (strcmp(bp->type, cp->lval) != 0) { Log(LOG_LEVEL_ERR, "Body type mismatch for body reference '%s' in promise at line %zu of file '%s', '%s' does not equal '%s'", body_name, pp->offset.line, PromiseGetBundle(pp)->source_path, bp->type, cp->lval); } /* Keep the referent body type as a boolean for convenience when checking later */ if (IsDefinedClass(ctx, cp->classes, PromiseGetNamespace(pcopy))) { Constraint *cp_copy = PromiseAppendConstraint(pcopy, cp->lval, (Rval) {xstrdup("true"), RVAL_TYPE_SCALAR }, false); cp_copy->offset = cp->offset; } if (bp->args != NULL) { /* There are arguments to insert */ if (fp == NULL || fp->args == NULL) { Log(LOG_LEVEL_ERR, "Argument mismatch for body reference '%s' in promise at line %zu of file '%s'", body_name, pp->offset.line, PromiseGetBundle(pp)->source_path); } for (size_t k = 0; k < SeqLength(bp->conlist); k++) { Constraint *scp = SeqAt(bp->conlist, k); returnval = ExpandPrivateRval(ctx, NULL, "body", scp->rval.item, scp->rval.type); if (IsDefinedClass(ctx, scp->classes, PromiseGetNamespace(pcopy))) { Constraint *scp_copy = PromiseAppendConstraint(pcopy, scp->lval, returnval, false); scp_copy->offset = scp->offset; } } } else { /* No arguments to deal with or body undeclared */ if (fp != NULL) { Log(LOG_LEVEL_ERR, "An apparent body \"%s()\" was undeclared or could have incorrect args, but used in a promise near line %zu of %s (possible unquoted literal value)", body_name, pp->offset.line, PromiseGetBundle(pp)->source_path); } else { for (size_t k = 0; k < SeqLength(bp->conlist); k++) { Constraint *scp = SeqAt(bp->conlist, k); Rval newrv = RvalCopy(scp->rval); if (newrv.type == RVAL_TYPE_LIST) { Rlist *new_list = RvalRlistValue(newrv); RlistFlatten(ctx, &new_list); newrv.item = new_list; } if (IsDefinedClass(ctx, scp->classes, PromiseGetNamespace(pcopy))) { Constraint *scp_copy = PromiseAppendConstraint(pcopy, scp->lval, newrv, false); scp_copy->offset = scp->offset; } } } } EvalContextStackPopFrame(ctx); } else { const Policy *policy = PolicyFromPromise(pp); if (cp->references_body && !IsBundle(policy->bundles, EmptyString(body_ns) ? NULL : body_ns, body_name)) { Log(LOG_LEVEL_ERR, "Apparent body \"%s()\" was undeclared, but used in a promise near line %zu of %s (possible unquoted literal value)", body_name, pp->offset.line, PromiseGetBundle(pp)->source_path); } Rval newrv = RvalCopy(cp->rval); if (newrv.type == RVAL_TYPE_LIST) { Rlist *new_list = RvalRlistValue(newrv); RlistFlatten(ctx, &new_list); newrv.item = new_list; } if (IsDefinedClass(ctx, cp->classes, PromiseGetNamespace(pcopy))) { Constraint *cp_copy = PromiseAppendConstraint(pcopy, cp->lval, newrv, false); cp_copy->offset = cp->offset; } } } return pcopy; }
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; }
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); }
FnCallResult FnCallEvaluate(EvalContext *ctx, FnCall *fp, const Promise *caller) { fp->caller = caller; if (!(ctx->eval_options & EVAL_OPTION_EVAL_FUNCTIONS)) { Log(LOG_LEVEL_VERBOSE, "Skipping function '%s', because evaluation was turned off in the evaluator", fp->name); return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } }; } else if (!EvalContextPromiseIsActive(ctx, caller)) { Log(LOG_LEVEL_VERBOSE, "Skipping function '%s', because it was excluded by classes", fp->name); return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } }; } const FnCallType *fp_type = FnCallTypeGet(fp->name); if (!fp_type) { if (caller) { Log(LOG_LEVEL_ERR, "No such FnCall '%s' in promise '%s' near line %zd", fp->name, PromiseGetBundle(caller)->source_path, caller->offset.line); } else { Log(LOG_LEVEL_ERR, "No such FnCall '%s', context info unavailable", fp->name); } return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } }; } Rlist *expargs = NewExpArgs(ctx, fp); if (UnresolvedArgs(expargs)) { DeleteExpArgs(expargs); return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } }; } Rval cached_rval; if ((fp_type->options & FNCALL_OPTION_CACHED) && EvalContextFunctionCacheGet(ctx, fp, expargs, &cached_rval)) { return (FnCallResult) { FNCALL_SUCCESS, RvalCopy(cached_rval) }; } FnCallResult result = CallFunction(ctx, fp, expargs); if (result.status == FNCALL_FAILURE) { DeleteExpArgs(expargs); return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } }; } if (fp_type->options & FNCALL_OPTION_CACHED) { EvalContextFunctionCachePut(ctx, fp, expargs, &result.rval); } DeleteExpArgs(expargs); return result; }
Rval EvaluateFinalRval(EvalContext *ctx, const Policy *policy, const char *ns, const char *scope, Rval rval, bool forcelist, const Promise *pp) { assert(ctx); assert(policy); Rval returnval, newret; if ((rval.type == RVAL_TYPE_SCALAR) && IsNakedVar(rval.item, '@')) /* Treat lists specially here */ { char naked[CF_MAXVARSIZE]; GetNaked(naked, rval.item); if (!IsExpandable(naked)) { VarRef *ref = VarRefParseFromScope(naked, scope); DataType value_type = DATA_TYPE_NONE; const void *value = EvalContextVariableGet(ctx, ref, &value_type); if (!value || DataTypeToRvalType(value_type) != RVAL_TYPE_LIST) { returnval = ExpandPrivateRval(ctx, NULL, "this", rval.item, rval.type); } else { returnval.item = ExpandList(ctx, ns, scope, value, true); returnval.type = RVAL_TYPE_LIST; } VarRefDestroy(ref); } else { returnval = ExpandPrivateRval(ctx, NULL, "this", rval.item, rval.type); } } else { if (forcelist) /* We are replacing scalar @(name) with list */ { returnval = ExpandPrivateRval(ctx, ns, scope, rval.item, rval.type); } else { if (FnCallIsBuiltIn(rval)) { returnval = RvalCopy(rval); } else { returnval = ExpandPrivateRval(ctx, NULL, "this", rval.item, rval.type); } } } switch (returnval.type) { case RVAL_TYPE_SCALAR: case RVAL_TYPE_CONTAINER: break; case RVAL_TYPE_LIST: for (Rlist *rp = RvalRlistValue(returnval); rp; rp = rp->next) { if (rp->val.type == RVAL_TYPE_FNCALL) { FnCall *fp = RlistFnCallValue(rp); FnCallResult res = FnCallEvaluate(ctx, policy, fp, pp); FnCallDestroy(fp); rp->val = res.rval; } else { if (EvalContextStackCurrentPromise(ctx)) { if (IsCf3VarString(RlistScalarValue(rp))) { newret = ExpandPrivateRval(ctx, NULL, "this", rp->val.item, rp->val.type); free(rp->val.item); rp->val.item = newret.item; } } } /* returnval unchanged */ } break; case RVAL_TYPE_FNCALL: if (FnCallIsBuiltIn(returnval)) { FnCall *fp = RvalFnCallValue(returnval); returnval = FnCallEvaluate(ctx, policy, fp, pp).rval; FnCallDestroy(fp); } break; default: returnval.item = NULL; returnval.type = RVAL_TYPE_NOPROMISEE; break; } return returnval; }
/** * @brief Flattens an Rlist by expanding naked scalar list-variable * members. Flattening is only one-level deep. */ void RlistFlatten(EvalContext *ctx, Rlist **list) { Rlist *next; for (Rlist *rp = *list; rp != NULL; rp = next) { next = rp->next; if (rp->val.type == RVAL_TYPE_SCALAR && IsNakedVar(RlistScalarValue(rp), '@')) { char naked[CF_MAXVARSIZE]; GetNaked(naked, RlistScalarValue(rp)); /* Make sure there are no inner expansions to take place, like if * rp was "@{blah_$(blue)}". */ if (!IsExpandable(naked)) { Log(LOG_LEVEL_DEBUG, "Flattening slist: %s", RlistScalarValue(rp)); VarRef *ref = VarRefParse(naked); DataType value_type; const void *value = EvalContextVariableGet(ctx, ref, &value_type); VarRefDestroy(ref); if (value_type == CF_DATA_TYPE_NONE) { assert(value == NULL); continue; /* undefined variable */ } if (DataTypeToRvalType(value_type) != RVAL_TYPE_LIST) { Log(LOG_LEVEL_WARNING, "'%s' failed - variable is not list but %s", RlistScalarValue(rp), DataTypeToString(value_type)); continue; } /* NOTE: Remember that value can be NULL as an empty Rlist. */ /* at_node: just a mnemonic name for the list node with @{blah}. */ Rlist *at_node = rp; Rlist *insert_after = at_node; for (const Rlist *rp2 = value; rp2 != NULL; rp2 = rp2->next) { assert(insert_after != NULL); RlistInsertAfter(insert_after, RvalCopy(rp2->val)); insert_after = insert_after->next; } /* Make sure we won't miss any element. */ assert(insert_after->next == next); RlistDestroyEntry(list, at_node); /* Delete @{blah} entry */ char *list_s = RlistToString(*list); Log(LOG_LEVEL_DEBUG, "Flattened slist: %s", list_s); free(list_s); } } } }
Promise *DeRefCopyPromise(EvalContext *ctx, const Promise *pp) { Promise *pcopy; Rval returnval; if (pp->promisee.item) { CfDebug("CopyPromise(%s->", pp->promiser); if (DEBUG) { RvalShow(stdout, pp->promisee); } CfDebug("\n"); } else { CfDebug("CopyPromise(%s->)\n", pp->promiser); } pcopy = xcalloc(1, sizeof(Promise)); if (pp->promiser) { pcopy->promiser = xstrdup(pp->promiser); } if (pp->promisee.item) { pcopy->promisee = RvalCopy(pp->promisee); if (pcopy->promisee.type == RVAL_TYPE_LIST) { Rlist *rval_list = RvalRlistValue(pcopy->promisee); RlistFlatten(ctx, &rval_list); pcopy->promisee.item = rval_list; } } if (pp->classes) { pcopy->classes = xstrdup(pp->classes); } /* FIXME: may it happen? */ if ((pp->promisee.item != NULL && pcopy->promisee.item == NULL)) { ProgrammingError("Unable to copy promise"); } pcopy->parent_promise_type = pp->parent_promise_type; pcopy->offset.line = pp->offset.line; pcopy->comment = pp->comment ? xstrdup(pp->comment) : NULL; pcopy->has_subbundles = pp->has_subbundles; pcopy->conlist = SeqNew(10, ConstraintDestroy); pcopy->org_pp = pp->org_pp; pcopy->offset = pp->offset; CfDebug("Copying promise constraints\n\n"); /* No further type checking should be necessary here, already done by CheckConstraintTypeMatch */ for (size_t i = 0; i < SeqLength(pp->conlist); i++) { Constraint *cp = SeqAt(pp->conlist, i); Body *bp = NULL; FnCall *fp = NULL; char *bodyname = NULL; /* A body template reference could look like a scalar or fn to the parser w/w () */ Policy *policy = PolicyFromPromise(pp); Seq *bodies = policy ? policy->bodies : NULL; switch (cp->rval.type) { case RVAL_TYPE_SCALAR: bodyname = (char *) cp->rval.item; if (cp->references_body) { bp = IsBody(bodies, PromiseGetNamespace(pp), bodyname); } fp = NULL; break; case RVAL_TYPE_FNCALL: fp = (FnCall *) cp->rval.item; bodyname = fp->name; bp = IsBody(bodies, PromiseGetNamespace(pp), bodyname); break; default: bp = NULL; fp = NULL; bodyname = NULL; break; } /* First case is: we have a body template to expand lval = body(args), .. */ if (bp) { EvalContextStackPushBodyFrame(ctx, bp); if (strcmp(bp->type, cp->lval) != 0) { Log(LOG_LEVEL_ERR, "Body type mismatch for body reference \"%s\" in promise at line %zu of %s (%s != %s)\n", bodyname, pp->offset.line, PromiseGetBundle(pp)->source_path, bp->type, cp->lval); } /* Keep the referent body type as a boolean for convenience when checking later */ { Constraint *cp_copy = PromiseAppendConstraint(pcopy, cp->lval, (Rval) {xstrdup("true"), RVAL_TYPE_SCALAR }, cp->classes, false); cp_copy->offset = cp->offset; } CfDebug("Handling body-lval \"%s\"\n", cp->lval); if (bp->args != NULL) { /* There are arguments to insert */ if (fp == NULL || fp->args == NULL) { Log(LOG_LEVEL_ERR, "Argument mismatch for body reference \"%s\" in promise at line %zu of %s", bodyname, pp->offset.line, PromiseGetBundle(pp)->source_path); } if (fp && bp && fp->args && bp->args && !ScopeMapBodyArgs(ctx, "body", fp->args, bp->args)) { Log(LOG_LEVEL_ERR, "Number of arguments does not match for body reference \"%s\" in promise at line %zu of %s\n", bodyname, pp->offset.line, PromiseGetBundle(pp)->source_path); } for (size_t k = 0; k < SeqLength(bp->conlist); k++) { Constraint *scp = SeqAt(bp->conlist, k); CfDebug("Doing arg-mapped sublval = %s (promises.c)\n", scp->lval); returnval = ExpandPrivateRval(ctx, "body", scp->rval); { Constraint *scp_copy = PromiseAppendConstraint(pcopy, scp->lval, returnval, scp->classes, false); scp_copy->offset = scp->offset; } } ScopeClear("body"); } else { /* No arguments to deal with or body undeclared */ if (fp != NULL) { Log(LOG_LEVEL_ERR, "An apparent body \"%s()\" was undeclared or could have incorrect args, but used in a promise near line %zu of %s (possible unquoted literal value)", bodyname, pp->offset.line, PromiseGetBundle(pp)->source_path); } else { for (size_t k = 0; k < SeqLength(bp->conlist); k++) { Constraint *scp = SeqAt(bp->conlist, k); CfDebug("Doing sublval = %s (promises.c)\n", scp->lval); Rval newrv = RvalCopy(scp->rval); if (newrv.type == RVAL_TYPE_LIST) { Rlist *new_list = RvalRlistValue(newrv); RlistFlatten(ctx, &new_list); newrv.item = new_list; } { Constraint *scp_copy = PromiseAppendConstraint(pcopy, scp->lval, newrv, scp->classes, false); scp_copy->offset = scp->offset; } } } } EvalContextStackPopFrame(ctx); } else { Policy *policy = PolicyFromPromise(pp); if (cp->references_body && !IsBundle(policy->bundles, bodyname)) { Log(LOG_LEVEL_ERR, "Apparent body \"%s()\" was undeclared, but used in a promise near line %zu of %s (possible unquoted literal value)", bodyname, pp->offset.line, PromiseGetBundle(pp)->source_path); } Rval newrv = RvalCopy(cp->rval); if (newrv.type == RVAL_TYPE_LIST) { Rlist *new_list = RvalRlistValue(newrv); RlistFlatten(ctx, &new_list); newrv.item = new_list; } { Constraint *cp_copy = PromiseAppendConstraint(pcopy, cp->lval, newrv, cp->classes, false); cp_copy->offset = cp->offset; } } } return pcopy; }
Rlist *RlistAppend(Rlist **start, const void *item, RvalType type) /* Allocates new memory for objects - careful, could leak! */ { Rlist *rp, *lp = *start; FnCall *fp; switch (type) { case RVAL_TYPE_SCALAR: return RlistAppendScalar(start, item); case RVAL_TYPE_FNCALL: CfDebug("Appending function to rval-list function call: "); fp = (FnCall *) item; if (DEBUG) { FnCallShow(stdout, fp); } CfDebug("\n"); break; case RVAL_TYPE_LIST: CfDebug("Expanding and appending list object\n"); for (rp = (Rlist *) item; rp != NULL; rp = rp->next) { lp = RlistAppend(start, rp->item, rp->type); } return lp; default: CfDebug("Cannot append %c to rval-list [%s]\n", type, (char *) item); return NULL; } rp = xmalloc(sizeof(Rlist)); if (*start == NULL) { *start = rp; } else { for (lp = *start; lp->next != NULL; lp = lp->next) { } lp->next = rp; } rp->item = RvalCopy((Rval) {(void *) item, type}).item; rp->type = type; /* scalar, builtin function */ ThreadLock(cft_lock); if (type == RVAL_TYPE_LIST) { rp->state_ptr = rp->item; } else { rp->state_ptr = NULL; } rp->next = NULL; ThreadUnlock(cft_lock); return rp; }
Rval EvaluateFinalRval(EvalContext *ctx, const Policy *policy, const char *ns, const char *scope, Rval rval, bool forcelist, const Promise *pp) { assert(ctx); assert(policy); Rval returnval; /* Treat lists specially. */ if (rval.type == RVAL_TYPE_SCALAR && IsNakedVar(rval.item, '@')) { char naked[CF_MAXVARSIZE]; GetNaked(naked, rval.item); if (IsExpandable(naked)) /* example: @(blah_$(blue)) */ { returnval = ExpandPrivateRval(ctx, NULL, "this", rval.item, rval.type); } else { VarRef *ref = VarRefParseFromScope(naked, scope); DataType value_type; const void *value = EvalContextVariableGet(ctx, ref, &value_type); VarRefDestroy(ref); if (DataTypeToRvalType(value_type) == RVAL_TYPE_LIST) { returnval.item = ExpandList(ctx, ns, scope, value, true); returnval.type = RVAL_TYPE_LIST; } else { returnval = ExpandPrivateRval(ctx, NULL, "this", rval.item, rval.type); } } } else if (forcelist) /* We are replacing scalar @(name) with list */ { returnval = ExpandPrivateRval(ctx, ns, scope, rval.item, rval.type); } else if (FnCallIsBuiltIn(rval)) { returnval = RvalCopy(rval); } else { returnval = ExpandPrivateRval(ctx, NULL, "this", rval.item, rval.type); } switch (returnval.type) { case RVAL_TYPE_SCALAR: case RVAL_TYPE_CONTAINER: break; case RVAL_TYPE_LIST: for (Rlist *rp = RvalRlistValue(returnval); rp; rp = rp->next) { switch (rp->val.type) { case RVAL_TYPE_FNCALL: { FnCall *fp = RlistFnCallValue(rp); rp->val = FnCallEvaluate(ctx, policy, fp, pp).rval; FnCallDestroy(fp); break; } case RVAL_TYPE_SCALAR: if (EvalContextStackCurrentPromise(ctx) && IsCf3VarString(RlistScalarValue(rp))) { void *prior = rp->val.item; rp->val = ExpandPrivateRval(ctx, NULL, "this", prior, RVAL_TYPE_SCALAR); free(prior); } /* else: returnval unchanged. */ break; default: assert(!"Bad type for entry in Rlist"); } } break; case RVAL_TYPE_FNCALL: if (FnCallIsBuiltIn(returnval)) { FnCall *fp = RvalFnCallValue(returnval); returnval = FnCallEvaluate(ctx, policy, fp, pp).rval; FnCallDestroy(fp); } break; default: assert(returnval.item == NULL); /* else we're leaking it */ returnval.item = NULL; returnval.type = RVAL_TYPE_NOPROMISEE; break; } return returnval; }
// See fncall.c for the usage of allow_all_types. Rlist *RlistAppendAllTypes(Rlist **start, const void *item, RvalType type, bool allow_all_types) { Rlist *lp = *start; switch (type) { case RVAL_TYPE_SCALAR: return RlistAppendScalar(start, item); case RVAL_TYPE_FNCALL: break; case RVAL_TYPE_LIST: if (allow_all_types) { JsonElement* store = JsonArrayCreate(RlistLen(item)); for (const Rlist *rp = item; rp; rp = rp->next) { JsonArrayAppendElement(store, RvalToJson(rp->val)); } return RlistAppendRval(start, (Rval) { store, RVAL_TYPE_CONTAINER }); } for (const Rlist *rp = item; rp; rp = rp->next) { lp = RlistAppendRval(start, RvalCopy(rp->val)); } return lp; case RVAL_TYPE_CONTAINER: if (allow_all_types) { return RlistAppendRval(start, (Rval) { JsonCopy((JsonElement*) item), RVAL_TYPE_CONTAINER }); } // note falls through! default: Log(LOG_LEVEL_DEBUG, "Cannot append %c to rval-list '%s'", type, (char *) item); return NULL; } Rlist *rp = xmalloc(sizeof(Rlist)); rp->val = RvalNew(item, type); rp->next = NULL; if (*start == NULL) { *start = rp; } else { for (lp = *start; lp->next != NULL; lp = lp->next) { } lp->next = rp; } return rp; }
FnCallResult FnCallEvaluate(EvalContext *ctx, const Policy *policy, FnCall *fp, const Promise *caller) { assert(ctx); assert(policy); assert(fp); fp->caller = caller; if (!EvalContextGetEvalOption(ctx, EVAL_OPTION_EVAL_FUNCTIONS)) { Log(LOG_LEVEL_VERBOSE, "Skipping function '%s', because evaluation was turned off in the evaluator", fp->name); return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } }; } const FnCallType *fp_type = FnCallTypeGet(fp->name); if (!fp_type) { if (caller) { Log(LOG_LEVEL_ERR, "No such FnCall '%s' in promise '%s' near line %zd", fp->name, PromiseGetBundle(caller)->source_path, caller->offset.line); } else { Log(LOG_LEVEL_ERR, "No such FnCall '%s', context info unavailable", fp->name); } return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } }; } Rlist *expargs = NewExpArgs(ctx, policy, fp); Writer *fncall_writer; const char *fncall_string; if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG) { fncall_writer = StringWriter(); FnCallWrite(fncall_writer, fp); fncall_string = StringWriterData(fncall_writer); } if (RlistIsUnresolved(expargs)) { if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG) { Log(LOG_LEVEL_DEBUG, "Skipping function evaluation for now," " arguments contain unresolved variables: %s", fncall_string); WriterClose(fncall_writer); } RlistDestroy(expargs); return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } }; } Rval cached_rval; if ((fp_type->options & FNCALL_OPTION_CACHED) && EvalContextFunctionCacheGet(ctx, fp, expargs, &cached_rval)) { if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG) { Log(LOG_LEVEL_DEBUG, "Using previously cached result for function: %s", fncall_string); WriterClose(fncall_writer); } Writer *w = StringWriter(); FnCallWrite(w, fp); WriterClose(w); RlistDestroy(expargs); return (FnCallResult) { FNCALL_SUCCESS, RvalCopy(cached_rval) }; } if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG) { Log(LOG_LEVEL_DEBUG, "Evaluating function: %s", fncall_string); WriterClose(fncall_writer); } FnCallResult result = CallFunction(ctx, policy, fp, expargs); if (result.status == FNCALL_FAILURE) { RlistDestroy(expargs); return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } }; } else if (result.rval.type == RVAL_TYPE_LIST && !result.rval.item) { Rlist *seq = NULL; // don't pass NULL items to evaluator RlistPrepend(&seq, CF_NULL_VALUE, RVAL_TYPE_SCALAR); result.rval.item = seq; } if (fp_type->options & FNCALL_OPTION_CACHED) { Writer *w = StringWriter(); FnCallWrite(w, fp); Log(LOG_LEVEL_VERBOSE, "Caching result for function '%s'", StringWriterData(w)); WriterClose(w); EvalContextFunctionCachePut(ctx, fp, expargs, &result.rval); } RlistDestroy(expargs); return result; }
FnCallResult FnCallEvaluate(EvalContext *ctx, const Policy *policy, FnCall *fp, const Promise *caller) { assert(ctx); assert(policy); assert(fp); fp->caller = caller; if (!EvalContextGetEvalOption(ctx, EVAL_OPTION_EVAL_FUNCTIONS)) { Log(LOG_LEVEL_VERBOSE, "Skipping function '%s', because evaluation was turned off in the evaluator", fp->name); return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } }; } const FnCallType *fp_type = FnCallTypeGet(fp->name); if (!fp_type) { if (caller) { Log(LOG_LEVEL_ERR, "No such FnCall '%s' in promise '%s' near line %zd", fp->name, PromiseGetBundle(caller)->source_path, caller->offset.line); } else { Log(LOG_LEVEL_ERR, "No such FnCall '%s', context info unavailable", fp->name); } return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } }; } Rlist *expargs = NewExpArgs(ctx, policy, fp, fp_type); Writer *fncall_writer = NULL; const char *fncall_string = ""; if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG) { fncall_writer = StringWriter(); FnCallWrite(fncall_writer, fp); fncall_string = StringWriterData(fncall_writer); } // Check if arguments are resolved, except for delayed evaluation functions if ( ! (fp_type->options & FNCALL_OPTION_DELAYED_EVALUATION) && RlistIsUnresolved(expargs)) { // Special case: ifelse(isvariable("x"), $(x), "default") // (the first argument will come down expanded as "!any") if (strcmp(fp->name, "ifelse") == 0 && RlistLen(expargs) == 3 && strcmp("!any", RlistScalarValueSafe(expargs)) == 0 && !RlistIsUnresolved(expargs->next->next)) { Log(LOG_LEVEL_DEBUG, "Allowing ifelse() function evaluation even" " though its arguments contain unresolved variables: %s", fncall_string); } else { if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG) { Log(LOG_LEVEL_DEBUG, "Skipping function evaluation for now," " arguments contain unresolved variables: %s", fncall_string); WriterClose(fncall_writer); } RlistDestroy(expargs); return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } }; } } Rval cached_rval; if ((fp_type->options & FNCALL_OPTION_CACHED) && EvalContextFunctionCacheGet(ctx, fp, expargs, &cached_rval)) { if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG) { Log(LOG_LEVEL_DEBUG, "Using previously cached result for function: %s", fncall_string); WriterClose(fncall_writer); } Writer *w = StringWriter(); FnCallWrite(w, fp); WriterClose(w); RlistDestroy(expargs); return (FnCallResult) { FNCALL_SUCCESS, RvalCopy(cached_rval) }; } if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG) { Log(LOG_LEVEL_DEBUG, "Evaluating function: %s", fncall_string); WriterClose(fncall_writer); } FnCallResult result = CallFunction(ctx, policy, fp, expargs); if (result.status == FNCALL_FAILURE) { RlistDestroy(expargs); return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } }; } if (fp_type->options & FNCALL_OPTION_CACHED) { Writer *w = StringWriter(); FnCallWrite(w, fp); Log(LOG_LEVEL_VERBOSE, "Caching result for function '%s'", StringWriterData(w)); WriterClose(w); EvalContextFunctionCachePut(ctx, fp, expargs, &result.rval); } RlistDestroy(expargs); return result; }
void RlistFlatten(EvalContext *ctx, Rlist **list) { Rlist *prev = NULL, *next; for (Rlist *rp = *list; rp != NULL; rp = next) { next = rp->next; if (rp->val.type != RVAL_TYPE_SCALAR) { prev = rp; continue; } char naked[CF_BUFSIZE] = ""; if (IsNakedVar(RlistScalarValue(rp), '@')) { GetNaked(naked, RlistScalarValue(rp)); if (!IsExpandable(naked)) { VarRef *ref = VarRefParse(naked); DataType value_type = CF_DATA_TYPE_NONE; const void *value = EvalContextVariableGet(ctx, ref, &value_type); VarRefDestroy(ref); if (value) { switch (DataTypeToRvalType(value_type)) { case RVAL_TYPE_LIST: { RlistDestroyEntry(list, rp); for (const Rlist *srp = value; srp != NULL; srp = srp->next) { Rlist *nrp = xmalloc(sizeof(Rlist)); nrp->val = RvalCopy(srp->val); nrp->next = next; if (prev) { prev->next = nrp; } else { *list = nrp; } prev = nrp; } } continue; default: Log(LOG_LEVEL_WARNING, "Attempted to dereference variable '%s' using @ but variable did not resolve to a list", RlistScalarValue(rp)); break; } } } } prev = rp; } }
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; }
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; }
FnCallResult FnCallEvaluate(EvalContext *ctx, const Policy *policy, FnCall *fp, const Promise *caller) { assert(ctx); assert(policy); assert(fp); fp->caller = caller; if (!EvalContextGetEvalOption(ctx, EVAL_OPTION_EVAL_FUNCTIONS)) { Log(LOG_LEVEL_VERBOSE, "Skipping function '%s', because evaluation was turned off in the evaluator", fp->name); return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } }; } else if (caller && !EvalContextPromiseIsActive(ctx, caller)) { Log(LOG_LEVEL_VERBOSE, "Skipping function '%s', because it was excluded by classes", fp->name); return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } }; } const FnCallType *fp_type = FnCallTypeGet(fp->name); if (!fp_type) { if (caller) { Log(LOG_LEVEL_ERR, "No such FnCall '%s' in promise '%s' near line %llu", fp->name, PromiseGetBundle(caller)->source_path, (unsigned long long)caller->offset.line); } else { Log(LOG_LEVEL_ERR, "No such FnCall '%s', context info unavailable", fp->name); } return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } }; } Rlist *expargs = NewExpArgs(ctx, policy, fp); if (RlistIsUnresolved(expargs)) { RlistDestroy(expargs); return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } }; } Rval cached_rval; if ((fp_type->options & FNCALL_OPTION_CACHED) && EvalContextFunctionCacheGet(ctx, fp, expargs, &cached_rval)) { Writer *w = StringWriter(); FnCallWrite(w, fp); Log(LOG_LEVEL_DEBUG, "Using previously cached result for function '%s'", StringWriterData(w)); WriterClose(w); RlistDestroy(expargs); return (FnCallResult) { FNCALL_SUCCESS, RvalCopy(cached_rval) }; } FnCallResult result = CallFunction(ctx, policy, fp, expargs); if (result.status == FNCALL_FAILURE) { RlistDestroy(expargs); return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } }; } if (fp_type->options & FNCALL_OPTION_CACHED) { Writer *w = StringWriter(); FnCallWrite(w, fp); Log(LOG_LEVEL_VERBOSE, "Caching result for function '%s'", StringWriterData(w)); WriterClose(w); EvalContextFunctionCachePut(ctx, fp, expargs, &result.rval); } RlistDestroy(expargs); return result; }