static void GetReturnValue(EvalContext *ctx, const Bundle *callee, const Promise *caller) { char *result = PromiseGetConstraintAsRval(caller, "useresult", RVAL_TYPE_SCALAR); if (result) { VarRef *ref = VarRefParseFromBundle("last-result", callee); VariableTableIterator *iter = EvalContextVariableTableIteratorNew(ctx, ref->ns, ref->scope, ref->lval); Variable *result_var = NULL; while ((result_var = VariableTableIteratorNext(iter))) { assert(result_var->ref->num_indices == 1); if (result_var->ref->num_indices != 1) { continue; } VarRef *new_ref = VarRefParseFromBundle(result, PromiseGetBundle(caller)); VarRefAddIndex(new_ref, result_var->ref->indices[0]); EvalContextVariablePut(ctx, new_ref, result_var->rval.item, result_var->type, "source=bundle"); VarRefDestroy(new_ref); } VarRefDestroy(ref); VariableTableIteratorDestroy(iter); } }
PromiseIterator *PromiseIteratorNew(EvalContext *ctx, const Promise *pp, const Rlist *lists, const Rlist *containers) { PromiseIterator *iter = xmalloc(sizeof(PromiseIterator)); iter->vars = SeqNew(RlistLen(lists), DeleteAssoc); iter->var_states = SeqNew(RlistLen(lists), NULL); iter->has_null_list = false; for (const Rlist *rp = lists; rp != NULL; rp = rp->next) { VarRef *ref = VarRefParseFromBundle(RlistScalarValue(rp), PromiseGetBundle(pp)); DataType dtype = CF_DATA_TYPE_NONE; const void *value = EvalContextVariableGet(ctx, ref, &dtype); if (!value) { Log(LOG_LEVEL_ERR, "Couldn't locate variable '%s' apparently in '%s'", RlistScalarValue(rp), PromiseGetBundle(pp)->name); VarRefDestroy(ref); continue; } VarRefDestroy(ref); CfAssoc *new_var = NewAssoc(RlistScalarValue(rp), (Rval) { (void *)value, DataTypeToRvalType(dtype) }, dtype); iter->has_null_list |= !AppendIterationVariable(iter, new_var); } for (const Rlist *rp = containers; rp; rp = rp->next) { VarRef *ref = VarRefParseFromBundle(RlistScalarValue(rp), PromiseGetBundle(pp)); DataType dtype = CF_DATA_TYPE_NONE; const JsonElement *value = EvalContextVariableGet(ctx, ref, &dtype); if (!value) { Log(LOG_LEVEL_ERR, "Couldn't locate variable '%s' apparently in '%s'", RlistScalarValue(rp), PromiseGetBundle(pp)->name); VarRefDestroy(ref); continue; } VarRefDestroy(ref); assert(dtype == CF_DATA_TYPE_CONTAINER); /* Mimics NewAssoc() but bypassing extra copying of ->rval: */ CfAssoc *new_var = xmalloc(sizeof(CfAssoc)); new_var->lval = xstrdup(RlistScalarValue(rp)); new_var->rval = (Rval) { ContainerToRlist(value), RVAL_TYPE_LIST }; new_var->dtype = CF_DATA_TYPE_STRING_LIST; iter->has_null_list |= !AppendIterationVariable(iter, new_var); } // We now have a control list of list-variables, with internal state in state_ptr return iter; }
static void CopyLocalizedReferencesToBundleScope(EvalContext *ctx, const Bundle *bundle, const Rlist *ref_names) { for (const Rlist *rp = ref_names; rp != NULL; rp = rp->next) { const char *mangled = RlistScalarValue(rp); char *demangled = xstrdup(mangled); DeMangleVarRefString(demangled, strlen(demangled)); if (strchr(RlistScalarValue(rp), CF_MAPPEDLIST)) { VarRef *demangled_ref = VarRefParseFromBundle(demangled, bundle); DataType value_type; const void *value = EvalContextVariableGet(ctx, demangled_ref, &value_type); if (!value) { ProgrammingError("Couldn't find extracted variable '%s'", mangled); } VarRef *mangled_ref = VarRefParseFromBundle(mangled, bundle); switch (DataTypeToRvalType(value_type)) { case RVAL_TYPE_LIST: { Rlist *list = RlistCopy(value); RlistFlatten(ctx, &list); EvalContextVariablePut(ctx, mangled_ref, list, value_type, "source=agent"); RlistDestroy(list); } break; case RVAL_TYPE_CONTAINER: case RVAL_TYPE_SCALAR: EvalContextVariablePut(ctx, mangled_ref, value, value_type, "source=agent"); break; case RVAL_TYPE_FNCALL: case RVAL_TYPE_NOPROMISEE: ProgrammingError("Illegal rval type in switch %d", DataTypeToRvalType(value_type)); } VarRefDestroy(mangled_ref); VarRefDestroy(demangled_ref); } free(demangled); } }
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 = 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) { VarRef *ref = VarRefParseFromBundle(naked, pbp); EvalContextVariableGet(ctx, ref, &retval, &vtype); VarRefDestroy(ref); } else { VarRef *ref = VarRefParseFromBundle(naked, bp); EvalContextVariableGet(ctx, ref, &retval, &vtype); VarRefDestroy(ref); } switch (vtype) { case DATA_TYPE_STRING_LIST: case DATA_TYPE_INT_LIST: case DATA_TYPE_REAL_LIST: { VarRef *ref = VarRefParseFromBundle(lval, bp); EvalContextVariablePut(ctx, ref, (Rval) { retval.item, RVAL_TYPE_LIST}, DATA_TYPE_STRING_LIST); VarRefDestroy(ref); } break; default: { Log(LOG_LEVEL_ERR, "List 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, (Rval) { rpr->item, RVAL_TYPE_SCALAR }, DATA_TYPE_STRING); VarRefDestroy(ref); } break; } } else { switch(rpr->type) { case RVAL_TYPE_SCALAR: { VarRef *ref = VarRefParseFromBundle(lval, bp); EvalContextVariablePut(ctx, ref, (Rval) { rpr->item, RVAL_TYPE_SCALAR }, DATA_TYPE_STRING); VarRefDestroy(ref); } break; case RVAL_TYPE_FNCALL: { FnCall *subfp = rpr->item; Rval rval = FnCallEvaluate(ctx, subfp, pp).rval; if (rval.type == RVAL_TYPE_SCALAR) { VarRef *ref = VarRefParseFromBundle(lval, bp); EvalContextVariablePut(ctx, ref, (Rval) { rval.item, RVAL_TYPE_SCALAR }, DATA_TYPE_STRING); VarRefDestroy(ref); } 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 */ return; }
PromiseResult VerifyReportPromise(EvalContext *ctx, const Promise *pp) { CfLock thislock; char unique_name[CF_EXPANDSIZE]; Attributes a = GetReportsAttributes(ctx, pp); // We let AcquireLock worry about making a unique name snprintf(unique_name, CF_EXPANDSIZE - 1, "%s", pp->promiser); thislock = AcquireLock(ctx, unique_name, VUQNAME, CFSTARTTIME, a.transaction, pp, false); // Handle return values before locks, as we always do this if (a.report.result) { // User-unwritable value last-result contains the useresult if (strlen(a.report.result) > 0) { snprintf(unique_name, CF_BUFSIZE, "last-result[%s]", a.report.result); } else { snprintf(unique_name, CF_BUFSIZE, "last-result"); } VarRef *ref = VarRefParseFromBundle(unique_name, PromiseGetBundle(pp)); EvalContextVariablePut(ctx, ref, pp->promiser, CF_DATA_TYPE_STRING, "source=bundle"); VarRefDestroy(ref); if (thislock.lock) { YieldCurrentLock(thislock); } return PROMISE_RESULT_NOOP; } if (thislock.lock == NULL) { return PROMISE_RESULT_SKIPPED; } PromiseBanner(ctx, pp); if (a.transaction.action == cfa_warn) { cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, a, "Need to repair reports promise: %s", pp->promiser); YieldCurrentLock(thislock); return PROMISE_RESULT_WARN; } if (a.report.to_file) { ReportToFile(a.report.to_file, pp->promiser); } else { ReportToLog(pp->promiser); } PromiseResult result = PROMISE_RESULT_NOOP; if (a.report.haveprintfile) { if (!PrintFile(a.report.filename, a.report.numlines)) { result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL); } } YieldCurrentLock(thislock); ClassAuditLog(ctx, pp, a, result); return result; }
static void ExpandAndMapIteratorsFromScalar(EvalContext *ctx, const Bundle *bundle, char *string, size_t length, int level, Rlist **scalars, Rlist **lists, Rlist **containers, Rlist **full_expansion) { assert(string); if (!string) { return; } Buffer *value = BufferNew(); for (size_t i = 0; i < length; i++) { const char *sp = string + i; Rlist *tmp_list = NULL; BufferZero(value); if (ExtractScalarPrefix(value, sp, length - i)) { if (full_expansion) { RlistConcatInto(&tmp_list, *full_expansion, BufferData(value)); RlistDestroy(*full_expansion); *full_expansion = tmp_list; tmp_list = NULL; } sp += BufferSize(value); i += BufferSize(value); BufferZero(value); if (i >= length) { break; } } if (*sp == '$') { BufferZero(value); ExtractScalarReference(value, sp, length - i, true); if (BufferSize(value) > 0) { Rlist *inner_expansion = NULL; Rlist *exp = NULL; int success = 0; VarRef *ref = VarRefParse(BufferData(value)); int increment = BufferSize(value) - 1 + 3; // Handle any embedded variables char *substring = string + i + 2; ExpandAndMapIteratorsFromScalar(ctx, bundle, substring, BufferSize(value), level+1, scalars, lists, containers, &inner_expansion); for (exp = inner_expansion; exp != NULL; exp = exp->next) { // If a list is non-local, i.e. $(bundle.var), map it to local $(bundle#var) // NB without modifying variables as we map them, it's not // possible to handle remote lists referenced by a variable // scope. For example: // scope => "test."; var => "somelist"; $($(scope)$(var)) fails // varname => "test.somelist"; $($(varname)) also fails // TODO Unless the consumer handles it? const char *inner_ref_str = RlistScalarValue(exp); VarRef *inner_ref = VarRefParseFromBundle(inner_ref_str, bundle); // var is the expanded name of the variable in its native context // finalname will be the mapped name in the local context "this." DataType value_type = DATA_TYPE_NONE; const void *value = EvalContextVariableGet(ctx, inner_ref, &value_type); if (value) { char *mangled_inner_ref = xstrdup(inner_ref_str); MangleVarRefString(mangled_inner_ref, strlen(mangled_inner_ref)); success++; switch (DataTypeToRvalType(value_type)) { case RVAL_TYPE_LIST: if (level > 0) { RlistPrependScalarIdemp(lists, mangled_inner_ref); } else { RlistAppendScalarIdemp(lists, mangled_inner_ref); } if (full_expansion) { for (const Rlist *rp = value; rp != NULL; rp = rp->next) { // append each slist item to each of full_expansion RlistConcatInto(&tmp_list, *full_expansion, RlistScalarValue(rp)); } } break; case RVAL_TYPE_SCALAR: RlistAppendScalarIdemp(scalars, mangled_inner_ref); if (full_expansion) { // append the scalar value to each of full_expansion RlistConcatInto(&tmp_list, *full_expansion, value); } break; case RVAL_TYPE_CONTAINER: if (level > 0) { RlistPrependScalarIdemp(containers, mangled_inner_ref); } else { RlistAppendScalarIdemp(containers, mangled_inner_ref); } break; case RVAL_TYPE_FNCALL: case RVAL_TYPE_NOPROMISEE: break; } free(mangled_inner_ref); } VarRefDestroy(inner_ref); } RlistDestroy(inner_expansion); if (full_expansion) { RlistDestroy(*full_expansion); *full_expansion = tmp_list; tmp_list = NULL; } // No need to map this.* even though it's technically qualified if (success && IsQualifiedVariable(BufferData(value)) && strcmp(ref->scope, "this") != 0) { char *dotpos = strchr(substring, '.'); if (dotpos) { *dotpos = CF_MAPPEDLIST; // replace '.' with '#' } if (strchr(BufferData(value), ':')) { char *colonpos = strchr(substring, ':'); if (colonpos) { *colonpos = '*'; } } } VarRefDestroy(ref); sp += increment; i += increment; } } } BufferDestroy(value); }
void VerifyReportPromise(EvalContext *ctx, Promise *pp) { Attributes a = { {0} }; CfLock thislock; char unique_name[CF_EXPANDSIZE]; a = GetReportsAttributes(ctx, pp); snprintf(unique_name, CF_EXPANDSIZE - 1, "%s_%zu", pp->promiser, pp->offset.line); thislock = AcquireLock(ctx, unique_name, VUQNAME, CFSTARTTIME, a.transaction, pp, false); // Handle return values before locks, as we always do this if (a.report.result) { // User-unwritable value last-result contains the useresult if (strlen(a.report.result) > 0) { snprintf(unique_name, CF_BUFSIZE, "last-result[%s]", a.report.result); } else { snprintf(unique_name, CF_BUFSIZE, "last-result"); } VarRef *ref = VarRefParseFromBundle(unique_name, PromiseGetBundle(pp)); EvalContextVariablePut(ctx, ref, (Rval) { pp->promiser, RVAL_TYPE_SCALAR }, DATA_TYPE_STRING); VarRefDestroy(ref); return; } // Now do regular human reports if (thislock.lock == NULL) { return; } PromiseBanner(pp); if (a.transaction.action == cfa_warn) { cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_WARN, pp, a, "Need to repair reports promise: %s", pp->promiser); YieldCurrentLock(thislock); return; } cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_CHANGE, pp, a, "Report: %s", pp->promiser); if (a.report.to_file) { ReportToFile(a.report.to_file, pp->promiser); } else { Log(LOG_LEVEL_NOTICE, "R: %s", pp->promiser); } if (a.report.haveprintfile) { PrintFile(ctx, a, pp); } if (a.report.showstate) { /* Do nothing. Deprecated. */ } if (a.report.havelastseen) { /* Do nothing. Deprecated. */ } YieldCurrentLock(thislock); }
PromiseResult VerifyMethod(EvalContext *ctx, const Rval call, Attributes a, const Promise *pp) { const Rlist *args = NULL; Buffer *method_name = BufferNew(); switch (call.type) { case RVAL_TYPE_FNCALL: { const FnCall *fp = RvalFnCallValue(call); ExpandScalar(ctx, PromiseGetBundle(pp)->ns, PromiseGetBundle(pp)->name, fp->name, method_name); args = fp->args; int arg_index = 0; while (args) { ++arg_index; if (strcmp(args->val.item, CF_NULL_VALUE) == 0) { Log(LOG_LEVEL_DEBUG, "Skipping invokation of method '%s' due to null-values in argument '%d'", fp->name, arg_index); BufferDestroy(method_name); return PROMISE_RESULT_SKIPPED; } args = args->next; } args = fp->args; EvalContextSetBundleArgs(ctx, args); } break; case RVAL_TYPE_SCALAR: { ExpandScalar(ctx, PromiseGetBundle(pp)->ns, PromiseGetBundle(pp)->name, RvalScalarValue(call), method_name); args = NULL; } break; default: BufferDestroy(method_name); return PROMISE_RESULT_NOOP; } char lockname[CF_BUFSIZE]; GetLockName(lockname, "method", pp->promiser, args); CfLock thislock = AcquireLock(ctx, lockname, VUQNAME, CFSTARTTIME, a.transaction, pp, false); if (thislock.lock == NULL) { BufferDestroy(method_name); return PROMISE_RESULT_SKIPPED; } PromiseBanner(ctx, pp); const Bundle *bp = EvalContextResolveBundleExpression(ctx, PromiseGetPolicy(pp), BufferData(method_name), "agent"); if (!bp) { bp = EvalContextResolveBundleExpression(ctx, PromiseGetPolicy(pp), BufferData(method_name), "common"); } PromiseResult result = PROMISE_RESULT_NOOP; if (bp) { if (a.transaction.action == cfa_warn) // don't skip for dry-runs (ie ignore DONTDO) { result = PROMISE_RESULT_WARN; cfPS(ctx, LOG_LEVEL_WARNING, result, pp, a, "Bundle '%s' should be invoked, but only a warning was promised!", BufferData(method_name)); } else { BundleBanner(bp, args); EvalContextStackPushBundleFrame(ctx, bp, args, a.inherit); /* Clear all array-variables that are already set in the sub-bundle. Otherwise, array-data accumulates between multiple bundle evaluations. Note: for bundles invoked multiple times via bundlesequence, array data *does* accumulate. */ VariableTableIterator *iter = EvalContextVariableTableIteratorNew(ctx, bp->ns, bp->name, NULL); Variable *var; while ((var = VariableTableIteratorNext(iter))) { if (!var->ref->num_indices) { continue; } EvalContextVariableRemove(ctx, var->ref); } VariableTableIteratorDestroy(iter); BundleResolve(ctx, bp); result = ScheduleAgentOperations(ctx, bp); GetReturnValue(ctx, bp, pp); EvalContextStackPopFrame(ctx); switch (result) { case PROMISE_RESULT_SKIPPED: // if a bundle returns 'skipped', meaning that all promises were locked in the bundle, // we explicitly consider the method 'kept' result = PROMISE_RESULT_NOOP; // intentional fallthru case PROMISE_RESULT_NOOP: cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "Method '%s' verified", bp->name); break; case PROMISE_RESULT_WARN: cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, a, "Method '%s' invoked repairs, but only warnings promised", bp->name); break; case PROMISE_RESULT_CHANGE: cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_CHANGE, pp, a, "Method '%s' invoked repairs", bp->name); break; case PROMISE_RESULT_FAIL: case PROMISE_RESULT_DENIED: cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Method '%s' failed in some repairs", bp->name); break; default: // PROMISE_RESULT_INTERRUPTED, TIMEOUT cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_FAIL, pp, a, "Method '%s' aborted in some repairs", bp->name); break; } } for (const Rlist *rp = bp->args; rp; rp = rp->next) { const char *lval = RlistScalarValue(rp); VarRef *ref = VarRefParseFromBundle(lval, bp); EvalContextVariableRemove(ctx, ref); VarRefDestroy(ref); } } else { if (IsCf3VarString(BufferData(method_name))) { Log(LOG_LEVEL_ERR, "A variable seems to have been used for the name of the method. In this case, the promiser also needs to contain the unique name of the method"); } cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "A method attempted to use a bundle '%s' that was apparently not defined", BufferData(method_name)); result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL); } YieldCurrentLock(thislock); BufferDestroy(method_name); EndBundleBanner(bp); return result; }
PromiseResult VerifyMethod(EvalContext *ctx, const Rval call, Attributes a, const Promise *pp) { assert(a.havebundle); const Rlist *args = NULL; Buffer *method_name = BufferNew(); switch (call.type) { case RVAL_TYPE_FNCALL: { const FnCall *fp = RvalFnCallValue(call); ExpandScalar(ctx, PromiseGetBundle(pp)->ns, PromiseGetBundle(pp)->name, fp->name, method_name); args = fp->args; } break; case RVAL_TYPE_SCALAR: { ExpandScalar(ctx, PromiseGetBundle(pp)->ns, PromiseGetBundle(pp)->name, RvalScalarValue(call), method_name); args = NULL; } break; default: BufferDestroy(method_name); return PROMISE_RESULT_NOOP; } char lockname[CF_BUFSIZE]; GetLockName(lockname, "method", pp->promiser, args); CfLock thislock = AcquireLock(ctx, lockname, VUQNAME, CFSTARTTIME, a.transaction, pp, false); if (thislock.lock == NULL) { BufferDestroy(method_name); return PROMISE_RESULT_SKIPPED; } PromiseBanner(pp); const Bundle *bp = EvalContextResolveBundleExpression(ctx, PromiseGetPolicy(pp), BufferData(method_name), "agent"); if (!bp) { bp = EvalContextResolveBundleExpression(ctx, PromiseGetPolicy(pp), BufferData(method_name), "common"); } PromiseResult result = PROMISE_RESULT_NOOP; if (bp) { if (a.transaction.action == cfa_warn) // don't skip for dry-runs (ie ignore DONTDO) { result = PROMISE_RESULT_WARN; cfPS(ctx, LOG_LEVEL_ERR, result, pp, a, "Bundle '%s' should be invoked, but only a warning was promised!", BufferData(method_name)); } else { BannerSubBundle(bp, args); EvalContextStackPushBundleFrame(ctx, bp, args, a.inherit); BundleResolve(ctx, bp); result = ScheduleAgentOperations(ctx, bp); GetReturnValue(ctx, bp, pp); EvalContextStackPopFrame(ctx); switch (result) { case PROMISE_RESULT_SKIPPED: // if a bundle returns 'skipped', meaning that all promises were locked in the bundle, // we explicitly consider the method 'kept' result = PROMISE_RESULT_NOOP; // intentional fallthru case PROMISE_RESULT_NOOP: cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "Method '%s' verified", bp->name); break; case PROMISE_RESULT_WARN: cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, a, "Method '%s' invoked repairs, but only warnings promised", bp->name); break; case PROMISE_RESULT_CHANGE: cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_CHANGE, pp, a, "Method '%s' invoked repairs", bp->name); break; case PROMISE_RESULT_FAIL: case PROMISE_RESULT_DENIED: cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Method '%s' failed in some repairs", bp->name); break; default: // PROMISE_RESULT_INTERRUPTED, TIMEOUT cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_FAIL, pp, a, "Method '%s' aborted in some repairs", bp->name); break; } } for (const Rlist *rp = bp->args; rp; rp = rp->next) { const char *lval = RlistScalarValue(rp); VarRef *ref = VarRefParseFromBundle(lval, bp); EvalContextVariableRemove(ctx, ref); VarRefDestroy(ref); } } else { if (IsCf3VarString(BufferData(method_name))) { Log(LOG_LEVEL_ERR, "A variable seems to have been used for the name of the method. In this case, the promiser also needs to contain the unique name of the method"); } cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "A method attempted to use a bundle '%s' that was apparently not defined", BufferData(method_name)); result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL); } YieldCurrentLock(thislock); BufferDestroy(method_name); return result; }
PromiseResult VerifyMethod(EvalContext *ctx, char *attrname, Attributes a, const Promise *pp) { Bundle *bp; void *vp; FnCall *fp; char method_name[CF_EXPANDSIZE]; Rlist *args = NULL; CfLock thislock; char lockname[CF_BUFSIZE]; if (a.havebundle) { if ((vp = PromiseGetConstraintAsRval(pp, attrname, RVAL_TYPE_FNCALL))) { fp = (FnCall *) vp; ExpandScalar(ctx, PromiseGetBundle(pp)->ns, PromiseGetBundle(pp)->name, fp->name, method_name); args = fp->args; } else if ((vp = PromiseGetConstraintAsRval(pp, attrname, RVAL_TYPE_SCALAR))) { ExpandScalar(ctx, PromiseGetBundle(pp)->ns, PromiseGetBundle(pp)->name, (char *) vp, method_name); args = NULL; } else { return PROMISE_RESULT_NOOP; } } GetLockName(lockname, "method", pp->promiser, args); thislock = AcquireLock(ctx, lockname, VUQNAME, CFSTARTTIME, a.transaction, pp, false); if (thislock.lock == NULL) { return PROMISE_RESULT_SKIPPED; } PromiseBanner(pp); char ns[CF_MAXVARSIZE] = ""; char bundle_name[CF_MAXVARSIZE] = ""; SplitScopeName(method_name, ns, bundle_name); bp = PolicyGetBundle(PolicyFromPromise(pp), EmptyString(ns) ? NULL : ns, "agent", bundle_name); if (!bp) { bp = PolicyGetBundle(PolicyFromPromise(pp), EmptyString(ns) ? NULL : ns, "common", bundle_name); } PromiseResult result = PROMISE_RESULT_NOOP; if (bp) { BannerSubBundle(bp, args); EvalContextStackPushBundleFrame(ctx, bp, args, a.inherit); BundleResolve(ctx, bp); result = ScheduleAgentOperations(ctx, bp); GetReturnValue(ctx, bp, pp); EvalContextStackPopFrame(ctx); switch (result) { case PROMISE_RESULT_FAIL: cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_FAIL, pp, a, "Method '%s' failed in some repairs or aborted", bp->name); break; case PROMISE_RESULT_CHANGE: cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_CHANGE, pp, a, "Method '%s' invoked repairs", bp->name); break; default: cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "Method '%s' verified", bp->name); break; } for (const Rlist *rp = bp->args; rp; rp = rp->next) { const char *lval = RlistScalarValue(rp); VarRef *ref = VarRefParseFromBundle(lval, bp); EvalContextVariableRemove(ctx, ref); VarRefDestroy(ref); } } else { if (IsCf3VarString(method_name)) { Log(LOG_LEVEL_ERR, "A variable seems to have been used for the name of the method. In this case, the promiser also needs to contain the unique name of the method"); } if (bp && (bp->name)) { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Method '%s' was used but was not defined", bp->name); result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL); } else { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "A method attempted to use a bundle '%s' that was apparently not defined", method_name); result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL); } } YieldCurrentLock(thislock); return result; }
void VerifyVarPromise(EvalContext *ctx, const Promise *pp, bool allow_duplicates) { ConvergeVariableOptions opts = CollectConvergeVariableOptions(ctx, pp, allow_duplicates); if (!opts.should_converge) { return; } //More consideration needs to be given to using these //a.transaction = GetTransactionConstraints(pp); Attributes a = { {0} }; a.classes = GetClassDefinitionConstraints(ctx, pp); VarRef *ref = VarRefParseFromBundle(pp->promiser, PromiseGetBundle(pp)); if (strcmp("meta", pp->parent_promise_type->name) == 0) { VarRefSetMeta(ref, true); } Rval existing_var_rval; DataType existing_var_type = DATA_TYPE_NONE; if (!IsExpandable(pp->promiser)) { EvalContextVariableGet(ctx, ref, &existing_var_rval, &existing_var_type); } 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 VarRefDestroy(ref); return; } FnCallResult res = FnCallEvaluate(ctx, fp, pp); if (res.status == FNCALL_FAILURE) { /* We do not assign variables to failed fn calls */ RvalDestroy(res.rval); VarRefDestroy(ref); return; } else { rval = res.rval; } } else { Buffer *conv = BufferNew(); if (strcmp(opts.cp_save->lval, "int") == 0) { int 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"); VarRefDestroy(ref); BufferDestroy(&conv); return; } rval = RvalCopy((Rval) {(char *)BufferData(conv), opts.cp_save->rval.type}); } else if (strcmp(opts.cp_save->lval, "real") == 0) { int result = -1; 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"); VarRefDestroy(ref); BufferDestroy(&conv); 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)->ns, 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, ref->ns, ref->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(ref->ns, ref->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); VarRefDestroy(ref); 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); VarRefDestroy(ref); 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, ref, rval, DataTypeFromString(opts.cp_save->lval))) { Log(LOG_LEVEL_VERBOSE, "Unable to converge %s.%s value (possibly empty or infinite regression)", ref->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); VarRefDestroy(ref); RvalDestroy(rval); }