static bool DistributeClass(EvalContext *ctx, const Rlist *dist, const Promise *pp) { int total = 0; const Rlist *rp; for (rp = dist; rp != NULL; rp = rp->next) { int result = IntFromString(RlistScalarValue(rp)); if (result < 0) { Log(LOG_LEVEL_ERR, "Negative integer in class distribution"); PromiseRef(LOG_LEVEL_ERR, pp); return false; } total += result; } if (total == 0) { Log(LOG_LEVEL_ERR, "An empty distribution was specified on RHS"); PromiseRef(LOG_LEVEL_ERR, pp); return false; } double fluct = drand48() * total; assert(0 <= fluct && fluct < total); for (rp = dist; rp != NULL; rp = rp->next) { fluct -= IntFromString(RlistScalarValue(rp)); if (fluct < 0) { break; } } assert(rp); char buffer[CF_MAXVARSIZE]; snprintf(buffer, CF_MAXVARSIZE, "%s_%s", pp->promiser, RlistScalarValue(rp)); if (strcmp(PromiseGetBundle(pp)->type, "common") == 0) { EvalContextClassPutSoft(ctx, buffer, CONTEXT_SCOPE_NAMESPACE, "source=promise"); } else { EvalContextClassPutSoft(ctx, buffer, CONTEXT_SCOPE_BUNDLE, "source=promise"); } return true; }
static bool SelectClass(EvalContext *ctx, const Rlist *list, const Promise *pp) { int count = 0; for (const Rlist *rp = list; rp != NULL; rp = rp->next) { count++; } if (count == 0) { Log(LOG_LEVEL_ERR, "No classes to select on RHS"); PromiseRef(LOG_LEVEL_ERR, pp); return false; } assert(list); char splay[CF_MAXVARSIZE]; snprintf(splay, CF_MAXVARSIZE, "%s+%s+%ju", VFQNAME, VIPADDRESS, (uintmax_t)getuid()); double hash = (double) StringHash(splay, 0, CF_HASHTABLESIZE); assert(hash < CF_HASHTABLESIZE); int n = (int) (count * hash / (double) CF_HASHTABLESIZE); assert(n < count); while (n > 0 && list->next != NULL) { n--; list = list->next; } EvalContextClassPutSoft(ctx, RlistScalarValue(list), CONTEXT_SCOPE_NAMESPACE, "source=promise"); return true; }
static PromiseResult VerifyProcessOp(EvalContext *ctx, Item *procdata, Attributes a, const Promise *pp) { bool do_signals = true; int out_of_range; int killed = 0; bool need_to_restart = true; Item *killlist = NULL; int matches = FindPidMatches(procdata, &killlist, a, pp->promiser); /* promise based on number of matches */ PromiseResult result = PROMISE_RESULT_NOOP; if (a.process_count.min_range != CF_NOINT) /* if a range is specified */ { if ((matches < a.process_count.min_range) || (matches > a.process_count.max_range)) { cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_CHANGE, pp, a, "Process count for '%s' was out of promised range (%d found)", pp->promiser, matches); result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE); for (const Rlist *rp = a.process_count.out_of_range_define; rp != NULL; rp = rp->next) { ClassRef ref = ClassRefParse(RlistScalarValue(rp)); EvalContextClassPutSoft(ctx, RlistScalarValue(rp), CONTEXT_SCOPE_NAMESPACE, "source=promise"); ClassRefDestroy(ref); } out_of_range = true; } else { for (const Rlist *rp = a.process_count.in_range_define; rp != NULL; rp = rp->next) { ClassRef ref = ClassRefParse(RlistScalarValue(rp)); EvalContextClassPutSoft(ctx, RlistScalarValue(rp), CONTEXT_SCOPE_NAMESPACE, "source=promise"); ClassRefDestroy(ref); } cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "Process promise for '%s' is kept", pp->promiser); out_of_range = false; } } else { out_of_range = true; } if (!out_of_range) { DeleteItemList(killlist); return result; } if (a.transaction.action == cfa_warn) { do_signals = false; result = PromiseResultUpdate(result, PROMISE_RESULT_WARN); } else { do_signals = true; } /* signal/kill promises for existing matches */ if (do_signals && (matches > 0)) { if (a.process_stop != NULL) { if (DONTDO) { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_WARN, pp, a, "Need to keep process-stop promise for '%s', but only a warning is promised", pp->promiser); result = PromiseResultUpdate(result, PROMISE_RESULT_WARN); } else { if (IsExecutable(CommandArg0(a.process_stop))) { ShellCommandReturnsZero(a.process_stop, SHELL_TYPE_NONE); } else { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Process promise to stop '%s' could not be kept because '%s' the stop operator failed", pp->promiser, a.process_stop); result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL); DeleteItemList(killlist); return result; } } } killed = DoAllSignals(ctx, killlist, a, pp, &result); } /* delegated promise to restart killed or non-existent entries */ need_to_restart = (a.restart_class != NULL) && (killed || (matches == 0)); DeleteItemList(killlist); if (!need_to_restart) { cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "No restart promised for %s", pp->promiser); return result; } else { if (a.transaction.action == cfa_warn) { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_WARN, pp, a, "Need to keep restart promise for '%s', but only a warning is promised", pp->promiser); result = PromiseResultUpdate(result, PROMISE_RESULT_WARN); } else { cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, a, "Making a one-time restart promise for '%s'", pp->promiser); result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE); EvalContextClassPutSoft(ctx, a.restart_class, CONTEXT_SCOPE_NAMESPACE, "source=promise"); } } return result; }
PromiseResult VerifyClassPromise(EvalContext *ctx, const Promise *pp, ARG_UNUSED void *param) { assert(param == NULL); Attributes a = GetClassContextAttributes(ctx, pp); if (!StringMatchFull("[a-zA-Z0-9_]+", pp->promiser)) { Log(LOG_LEVEL_VERBOSE, "Class identifier '%s' contains illegal characters - canonifying", pp->promiser); xsnprintf(pp->promiser, strlen(pp->promiser) + 1, "%s", CanonifyName(pp->promiser)); } if (a.context.nconstraints == 0) { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "No constraints for class promise '%s'", pp->promiser); return PROMISE_RESULT_FAIL; } if (a.context.nconstraints > 1) { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Irreconcilable constraints in classes for '%s'", pp->promiser); return PROMISE_RESULT_FAIL; } if (EvalClassExpression(ctx, a.context.expression, pp)) { if (!ValidClassName(pp->promiser)) { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Attempted to name a class '%s', which is an illegal class identifier", pp->promiser); return PROMISE_RESULT_FAIL; } else { char *tags = NULL; { Buffer *tag_buffer = BufferNew(); BufferAppendString(tag_buffer, "classes promise,attribute_name=label,source=promise"); for (const Rlist *rp = PromiseGetConstraintAsList(ctx, "meta", pp); rp; rp = rp->next) { BufferAppendChar(tag_buffer, ','); BufferAppendString(tag_buffer, RlistScalarValue(rp)); } tags = BufferClose(tag_buffer); } if (/* Persistent classes are always global: */ a.context.persistent > 0 || /* Namespace-scope is global: */ a.context.scope == CONTEXT_SCOPE_NAMESPACE || /* If there is no explicit scope, common bundles define global * classes, other bundles define local classes: */ (a.context.scope == CONTEXT_SCOPE_NONE && 0 == strcmp(PromiseGetBundle(pp)->type, "common"))) { Log(LOG_LEVEL_VERBOSE, "C: + Global class: %s ", pp->promiser); EvalContextClassPutSoft(ctx, pp->promiser, CONTEXT_SCOPE_NAMESPACE, tags); } else { Log(LOG_LEVEL_VERBOSE, "C: + Private class: %s ", pp->promiser); EvalContextClassPutSoft(ctx, pp->promiser, CONTEXT_SCOPE_BUNDLE, tags); } if (a.context.persistent > 0) { Log(LOG_LEVEL_VERBOSE, "C: + Persistent class: '%s'. (%d minutes)", pp->promiser, a.context.persistent); EvalContextHeapPersistentSave(ctx, pp->promiser, a.context.persistent, CONTEXT_STATE_POLICY_RESET, tags); } free(tags); return PROMISE_RESULT_NOOP; } } return PROMISE_RESULT_NOOP; }
static int EvalClassExpression(EvalContext *ctx, Constraint *cp, const Promise *pp) { assert(pp); int result_and = true; int result_or = false; int result_xor = 0; int result = 0, total = 0; char buffer[CF_MAXVARSIZE]; Rlist *rp; if (cp == NULL) // ProgrammingError ? We'll crash RSN anyway ... { Log(LOG_LEVEL_ERR, "EvalClassExpression internal diagnostic discovered an ill-formed condition"); } if (!IsDefinedClass(ctx, pp->classes)) { return false; } if (IsDefinedClass(ctx, pp->promiser)) { if (PromiseGetConstraintAsInt(ctx, "persistence", pp) == 0) { Log(LOG_LEVEL_VERBOSE, " ?> Cancelling cached persistent class %s", pp->promiser); EvalContextHeapPersistentRemove(pp->promiser); } return false; } switch (cp->rval.type) { Rval rval; FnCall *fp; case RVAL_TYPE_FNCALL: fp = RvalFnCallValue(cp->rval); /* Special expansion of functions for control, best effort only: */ FnCallResult res = FnCallEvaluate(ctx, PromiseGetPolicy(pp), fp, pp); FnCallDestroy(fp); cp->rval = res.rval; break; case RVAL_TYPE_LIST: for (rp = (Rlist *) cp->rval.item; rp != NULL; rp = rp->next) { rval = EvaluateFinalRval(ctx, PromiseGetPolicy(pp), NULL, "this", rp->val, true, pp); RvalDestroy(rp->val); rp->val = rval; } break; default: rval = ExpandPrivateRval(ctx, NULL, "this", cp->rval.item, cp->rval.type); RvalDestroy(cp->rval); cp->rval = rval; break; } if (strcmp(cp->lval, "expression") == 0) { return (cp->rval.type == RVAL_TYPE_SCALAR && IsDefinedClass(ctx, RvalScalarValue(cp->rval))); } if (strcmp(cp->lval, "not") == 0) { return (cp->rval.type == RVAL_TYPE_SCALAR && !IsDefinedClass(ctx, RvalScalarValue(cp->rval))); } // Class selection if (strcmp(cp->lval, "select_class") == 0) { char splay[CF_MAXVARSIZE]; int i, n; double hash; total = 0; for (rp = (Rlist *) cp->rval.item; rp != NULL; rp = rp->next) { total++; } if (total == 0) { Log(LOG_LEVEL_ERR, "No classes to select on RHS"); PromiseRef(LOG_LEVEL_ERR, pp); return false; } snprintf(splay, CF_MAXVARSIZE, "%s+%s+%ju", VFQNAME, VIPADDRESS, (uintmax_t)getuid()); hash = (double) StringHash(splay, 0, CF_HASHTABLESIZE); n = (int) (total * hash / (double) CF_HASHTABLESIZE); for (rp = (Rlist *) cp->rval.item, i = 0; rp != NULL; rp = rp->next, i++) { if (i == n) { EvalContextClassPutSoft(ctx, RlistScalarValue(rp), CONTEXT_SCOPE_NAMESPACE, "source=promise"); return true; } } } /* If we get here, anything remaining on the RHS must be a clist */ if (cp->rval.type != RVAL_TYPE_LIST) { Log(LOG_LEVEL_ERR, "RHS of promise body attribute '%s' is not a list", cp->lval); PromiseRef(LOG_LEVEL_ERR, pp); return true; } // Class distributions if (strcmp(cp->lval, "dist") == 0) { for (rp = (Rlist *) cp->rval.item; rp != NULL; rp = rp->next) { result = IntFromString(RlistScalarValue(rp)); if (result < 0) { Log(LOG_LEVEL_ERR, "Non-positive integer in class distribution"); PromiseRef(LOG_LEVEL_ERR, pp); return false; } total += result; } if (total == 0) { Log(LOG_LEVEL_ERR, "An empty distribution was specified on RHS"); PromiseRef(LOG_LEVEL_ERR, pp); return false; } double fluct = drand48(); double cum = 0.0; for (rp = (Rlist *) cp->rval.item; rp != NULL; rp = rp->next) { double prob = ((double) IntFromString(RlistScalarValue(rp))) / ((double) total); cum += prob; if (fluct < cum) { break; } } snprintf(buffer, CF_MAXVARSIZE - 1, "%s_%s", pp->promiser, RlistScalarValue(rp)); if (strcmp(PromiseGetBundle(pp)->type, "common") == 0) { EvalContextClassPutSoft(ctx, buffer, CONTEXT_SCOPE_NAMESPACE, "source=promise"); } else { EvalContextClassPutSoft(ctx, buffer, CONTEXT_SCOPE_BUNDLE, "source=promise"); } return true; } /* and/or/xor expressions */ enum { c_or = 0, c_and, c_xor } logic; if (strcmp(cp->lval, "or") == 0) { logic = c_or; } else if (strcmp(cp->lval, "and") == 0) { logic = c_and; } else if (strcmp(cp->lval, "xor") == 0) { logic = c_xor; } for (rp = (Rlist *) cp->rval.item; rp != NULL; rp = rp->next) { // tolerate unexpanded entries here and interpret as "class not set" if (rp->val.type != RVAL_TYPE_SCALAR) { result = false; } else { result = IsDefinedClass(ctx, RlistScalarValue(rp)); } // shortcut and and or switch (logic) { case c_or: if (result) { return true; } break; case c_and: if (!result) { return false; } break; default: break; } result_and = result_and && result; result_or = result_or || result; result_xor ^= result; } // Class combinations switch (logic) { case c_or: return result_or; case c_xor: return result_xor == 1; case c_and: return result_and; } return false; }