1 /*
2 Copyright 2021 Northern.tech AS
3
4 This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; version 3.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18
19 To the extent this program is licensed as part of the Enterprise
20 versions of CFEngine, the applicable Commercial Open Source License
21 (COSL) may apply to this file if you as a licensee so wish it. See
22 included file COSL.txt.
23 */
24
25 #include <verify_vars.h>
26
27 #include <actuator.h>
28 #include <attributes.h>
29 #include <regex.h> /* CompileRegex,StringMatchFullWithPrecompiledRegex */
30 #include <buffer.h>
31 #include <misc_lib.h>
32 #include <fncall.h>
33 #include <rlist.h>
34 #include <conversion.h>
35 #include <expand.h>
36 #include <scope.h>
37 #include <promises.h>
38 #include <vars.h>
39 #include <matching.h>
40 #include <syntax.h>
41 #include <audit.h>
42 #include <string_lib.h>
43 #include <cleanup.h>
44
45 typedef struct
46 {
47 bool should_converge;
48 bool ok_redefine;
49 bool drop_undefined;
50 Constraint *cp_save; // e.g. string => "foo"
51 } ConvergeVariableOptions;
52
53
54 static ConvergeVariableOptions CollectConvergeVariableOptions(EvalContext *ctx, const Promise *pp);
55 static bool Epimenides(EvalContext *ctx, const char *ns, const char *scope, const char *var, Rval rval, int level);
56 static bool CompareRval(const void *rval1_item, RvalType rval1_type, const void *rval2_item, RvalType rval2_type);
57
IsLegalVariableName(EvalContext * ctx,const Promise * pp)58 static bool IsLegalVariableName(EvalContext *ctx, const Promise *pp)
59 {
60 const char *var_name = pp->promiser;
61
62 /* TODO: remove at some point (global, leaked), but for now
63 * this offers an attractive speedup. */
64 static pcre *rx = NULL;
65 if (!rx)
66 {
67 /* \200-\377 is there for multibyte unicode characters */
68 rx = CompileRegex("[a-zA-Z0-9_\200-\377.]+(\\[.+\\])*"); /* Known leak, see TODO. */
69 }
70
71 if (!StringMatchFullWithPrecompiledRegex(rx, var_name))
72 {
73 return false;
74 }
75
76 char *bracket = strchr(var_name, '[');
77 char *dot = strchr(var_name, '.');
78 /* we only care about variable name prefix, not dots inside array keys */
79 if ((dot != NULL) && ((bracket == NULL) || (dot < bracket)))
80 {
81 /* detect and prevent remote bundle variable injection (CFE-1915) */
82 char *prefix = xstrndup(var_name, dot - var_name);
83 const Bundle *cur_bundle = PromiseGetBundle(pp);
84
85 if (!StringEqual(prefix, cur_bundle->name))
86 {
87 Log(LOG_LEVEL_VERBOSE,
88 "Variable '%s' may be attempted to be injected into a remote bundle",
89 var_name);
90 if (StringSetContains(EvalContextGetBundleNames(ctx), prefix))
91 {
92 Log(LOG_LEVEL_ERR, "Remote bundle variable injection detected!");
93 free(prefix);
94 return false;
95 }
96 /* the conflicting bundle may be defined later, we need to remember
97 * this promise as potentially dangerous */
98 EvalContextPushRemoteVarPromise(ctx, prefix, pp->org_pp);
99 }
100 free(prefix);
101 }
102
103 return true;
104 }
105
106 // TODO why not printing that new definition is skipped?
107 // TODO what with ifdefined?
108
VerifyVarPromise(EvalContext * ctx,const Promise * pp,ARG_UNUSED void * param)109 PromiseResult VerifyVarPromise(EvalContext *ctx, const Promise *pp,
110 ARG_UNUSED void *param)
111 {
112 assert(pp != NULL);
113
114 ConvergeVariableOptions opts = CollectConvergeVariableOptions(ctx, pp);
115
116 Log(LOG_LEVEL_DEBUG, "Evaluating vars promise: %s", pp->promiser);
117 LogDebug(LOG_MOD_VARS,
118 "ok_redefine=%d, drop_undefined=%d, should_converge=%d",
119 opts.ok_redefine, opts.drop_undefined, opts.should_converge);
120
121 if (!opts.should_converge)
122 {
123 LogDebug(LOG_MOD_VARS,
124 "Skipping vars promise because should_converge=false");
125 return PROMISE_RESULT_NOOP;
126 }
127
128 // opts.drop_undefined = true; /* always remove @{unresolved_list} */
129
130 Attributes a = ZeroAttributes;
131 // More consideration needs to be given to using these
132 //a.transaction = GetTransactionConstraints(pp);
133
134 /* Warn if promise locking was used with a promise that doesn't support it
135 * (which applies to all of 'vars', 'meta' and 'defaults' promises handled
136 * by this code).
137 * 'ifelapsed => "0"' (e.g. with 'action => immediate') can however be used
138 * to make sure cached functions are called every time. [ENT-7478]
139 * (Only do this in the first pass in cf-promises, we don't have to repeat
140 * the warning over and over.) */
141 if (EvalContextGetPass(ctx) == 0 && THIS_AGENT_TYPE == AGENT_TYPE_COMMON)
142 {
143 int ifelapsed = PromiseGetConstraintAsInt(ctx, "ifelapsed", pp);
144 if ((ifelapsed != CF_NOINT) &&
145 ((ifelapsed != 0) || !StringEqual(PromiseGetPromiseType(pp), "vars")))
146 {
147 Log(LOG_LEVEL_WARNING,
148 "ifelapsed attribute specified in action body for %s promise '%s',"
149 " but %s promises do not support promise locking",
150 PromiseGetPromiseType(pp), pp->promiser,
151 PromiseGetPromiseType(pp));
152 }
153 int expireafter = PromiseGetConstraintAsInt(ctx, "expireafter", pp);
154 if (expireafter != CF_NOINT)
155 {
156 Log(LOG_LEVEL_WARNING,
157 "expireafter attribute specified in action body for %s promise '%s',"
158 " but %s promises do not support promise locking",
159 PromiseGetPromiseType(pp), pp->promiser,
160 PromiseGetPromiseType(pp));
161 }
162 }
163
164 a.classes = GetClassDefinitionConstraints(ctx, pp);
165
166 VarRef *ref = VarRefParseFromBundle(pp->promiser, PromiseGetBundle(pp));
167 if (strcmp("meta", PromiseGetPromiseType(pp)) == 0)
168 {
169 VarRefSetMeta(ref, true);
170 }
171
172 DataType existing_value_type = CF_DATA_TYPE_NONE;
173 const void *existing_value;
174 if (IsExpandable(pp->promiser))
175 {
176 existing_value = NULL;
177 }
178 else
179 {
180 existing_value = EvalContextVariableGet(ctx, ref, &existing_value_type);
181 }
182
183 Rval rval = opts.cp_save->rval;
184 PromiseResult result;
185
186 if (rval.item != NULL || rval.type == RVAL_TYPE_LIST)
187 {
188 DataType data_type = DataTypeFromString(opts.cp_save->lval);
189
190 if (opts.cp_save->rval.type == RVAL_TYPE_FNCALL)
191 {
192 FnCall *fp = RvalFnCallValue(rval);
193 const FnCallType *fn = FnCallTypeGet(fp->name);
194 if (!fn)
195 {
196 assert(false && "Canary: should have been caught before this point");
197 FatalError(ctx, "While setting variable '%s' in bundle '%s', unknown function '%s'",
198 pp->promiser, PromiseGetBundle(pp)->name, fp->name);
199 }
200
201 if (fn->dtype != DataTypeFromString(opts.cp_save->lval))
202 {
203 FatalError(ctx, "While setting variable '%s' in bundle '%s', variable declared type '%s' but function '%s' returns type '%s'",
204 pp->promiser, PromiseGetBundle(pp)->name, opts.cp_save->lval,
205 fp->name, DataTypeToString(fn->dtype));
206 }
207
208 if (existing_value_type != CF_DATA_TYPE_NONE)
209 {
210 // Already did this
211 VarRefDestroy(ref);
212 return PROMISE_RESULT_NOOP;
213 }
214
215 FnCallResult res = FnCallEvaluate(ctx, PromiseGetPolicy(pp), fp, pp);
216
217 if (res.status == FNCALL_FAILURE)
218 {
219 /* We do not assign variables to failed fn calls */
220 if (EvalContextGetPass(ctx) == CF_DONEPASSES-1) {
221 // If we still fail at last pass, make a log
222 Log(LOG_LEVEL_VERBOSE, "While setting variable '%s' in bundle '%s', function '%s' failed - skipping",
223 pp->promiser, PromiseGetBundle(pp)->name, fp->name);
224 }
225 RvalDestroy(res.rval);
226 VarRefDestroy(ref);
227 return PROMISE_RESULT_NOOP;
228 }
229 else
230 {
231 rval = res.rval;
232 }
233 }
234 else
235 {
236 Buffer *conv = BufferNew();
237 bool malformed = false, misprint = false;
238
239 if (strcmp(opts.cp_save->lval, "int") == 0)
240 {
241 long int asint = IntFromString(opts.cp_save->rval.item);
242 if (asint == CF_NOINT)
243 {
244 malformed = true;
245 }
246 else if (0 > BufferPrintf(conv, "%ld", asint))
247 {
248 misprint = true;
249 }
250 else
251 {
252 rval = RvalNew(BufferData(conv), opts.cp_save->rval.type);
253 }
254 }
255 else if (strcmp(opts.cp_save->lval, "real") == 0)
256 {
257 double real_value;
258 if (!DoubleFromString(opts.cp_save->rval.item, &real_value))
259 {
260 malformed = true;
261 }
262 else if (0 > BufferPrintf(conv, "%lf", real_value))
263 {
264 misprint = true;
265 }
266 else
267 {
268 rval = RvalNew(BufferData(conv), opts.cp_save->rval.type);
269 }
270 }
271 else
272 {
273 rval = RvalCopy(opts.cp_save->rval);
274 }
275 BufferDestroy(conv);
276
277 if (malformed)
278 {
279 /* Arises when opts->cp_save->rval.item isn't yet expanded. */
280 /* Has already been logged by *FromString */
281 VarRefDestroy(ref);
282 return PROMISE_RESULT_FAIL;
283 }
284 else if (misprint)
285 {
286 /* Even though no problems with memory allocation can
287 * get here, there might be other problems. */
288 UnexpectedError("Problems writing to buffer");
289 VarRefDestroy(ref);
290 return PROMISE_RESULT_FAIL;
291 }
292 else if (rval.type == RVAL_TYPE_LIST)
293 {
294 Rlist *rval_list = RvalRlistValue(rval);
295 RlistFlatten(ctx, &rval_list);
296 rval.item = rval_list;
297 }
298 }
299
300 if (Epimenides(ctx, PromiseGetBundle(pp)->ns, PromiseGetBundle(pp)->name, pp->promiser, rval, 0))
301 {
302 Log(LOG_LEVEL_ERR, "Variable '%s' contains itself indirectly - an unkeepable promise", pp->promiser);
303 DoCleanupAndExit(EXIT_FAILURE);
304 }
305 else
306 {
307 /* See if the variable needs recursively expanding again */
308
309 Rval returnval = EvaluateFinalRval(ctx, PromiseGetPolicy(pp), ref->ns, ref->scope, rval, true, pp);
310
311 RvalDestroy(rval);
312
313 // freed before function exit
314 rval = returnval;
315 }
316
317 /* If variable did resolve but we're not allowed to modify it. */
318 /* ok_redefine: only on second iteration, else we ignore broken promises. TODO wat? */
319 if (existing_value_type != CF_DATA_TYPE_NONE &&
320 !opts.ok_redefine)
321 {
322 if (!CompareRval(existing_value, DataTypeToRvalType(existing_value_type),
323 rval.item, rval.type))
324 {
325 switch (rval.type)
326 {
327 /* TODO redefinition shouldn't be mentioned. Maybe handle like normal variable definition/ */
328 case RVAL_TYPE_SCALAR:
329 Log(LOG_LEVEL_VERBOSE, "V: Skipping redefinition of constant scalar '%s': from '%s' to '%s'",
330 pp->promiser, (const char *)existing_value, RvalScalarValue(rval));
331 PromiseRef(LOG_LEVEL_VERBOSE, pp);
332 break;
333
334 case RVAL_TYPE_LIST:
335 {
336 Log(LOG_LEVEL_VERBOSE, "V: Skipping redefinition of constant list '%s'", pp->promiser);
337 Writer *w = StringWriter();
338 RlistWrite(w, existing_value);
339 char *oldstr = StringWriterClose(w);
340 Log(LOG_LEVEL_DEBUG, "Old value: %s", oldstr);
341 free(oldstr);
342
343 w = StringWriter();
344 RlistWrite(w, rval.item);
345 char *newstr = StringWriterClose(w);
346 Log(LOG_LEVEL_DEBUG, "Skipped new value: %s", newstr);
347 free(newstr);
348
349 PromiseRef(LOG_LEVEL_VERBOSE, pp);
350 }
351 break;
352
353 case RVAL_TYPE_CONTAINER:
354 case RVAL_TYPE_FNCALL:
355 case RVAL_TYPE_NOPROMISEE:
356 break;
357 }
358 }
359
360 RvalDestroy(rval);
361 VarRefDestroy(ref);
362 return PROMISE_RESULT_NOOP;
363 }
364
365 if (IsCf3VarString(pp->promiser))
366 {
367 // Unexpanded variables, we don't do anything with
368 RvalDestroy(rval);
369 VarRefDestroy(ref);
370 return PROMISE_RESULT_NOOP;
371 }
372
373 if (!IsLegalVariableName(ctx, pp))
374 {
375 Log(LOG_LEVEL_ERR, "Variable identifier '%s' is not legal", pp->promiser);
376 PromiseRef(LOG_LEVEL_ERR, pp);
377 RvalDestroy(rval);
378 VarRefDestroy(ref);
379 return PROMISE_RESULT_NOOP;
380 }
381
382 if (rval.type == RVAL_TYPE_LIST)
383 {
384 if (opts.drop_undefined)
385 {
386 Rlist *stripped = RvalRlistValue(rval);
387 Rlist *entry = stripped;
388 while (entry)
389 {
390 Rlist *delete_me = NULL;
391 if (IsNakedVar(RlistScalarValue(entry), '@'))
392 {
393 delete_me = entry;
394 }
395 entry = entry->next;
396 RlistDestroyEntry(&stripped, delete_me);
397 }
398 rval.item = stripped;
399 }
400
401 for (const Rlist *rp = RvalRlistValue(rval); rp; rp = rp->next)
402 {
403 if (rp->val.type != RVAL_TYPE_SCALAR)
404 {
405 // Cannot assign variable because value is a list containing a non-scalar item
406 VarRefDestroy(ref);
407 RvalDestroy(rval);
408 return PROMISE_RESULT_NOOP;
409 }
410 }
411 }
412
413 if (ref->num_indices > 0)
414 {
415 if (data_type == CF_DATA_TYPE_CONTAINER)
416 {
417 char *lval_str = VarRefToString(ref, true);
418 Log(LOG_LEVEL_ERR, "Cannot assign a container to an indexed variable name '%s'. Should be assigned to '%s' instead",
419 lval_str, ref->lval);
420 free(lval_str);
421 VarRefDestroy(ref);
422 RvalDestroy(rval);
423 return PROMISE_RESULT_NOOP;
424 }
425 else
426 {
427 DataType existing_type;
428 VarRef *base_ref = VarRefCopyIndexless(ref);
429 if (EvalContextVariableGet(ctx, ref, &existing_type) && existing_type == CF_DATA_TYPE_CONTAINER)
430 {
431 char *lval_str = VarRefToString(ref, true);
432 char *base_ref_str = VarRefToString(base_ref, true);
433 Log(LOG_LEVEL_ERR, "Cannot assign value to indexed variable name '%s', because a container is already assigned to the base name '%s'",
434 lval_str, base_ref_str);
435 free(lval_str);
436 free(base_ref_str);
437 VarRefDestroy(base_ref);
438 VarRefDestroy(ref);
439 RvalDestroy(rval);
440 return PROMISE_RESULT_NOOP;
441 }
442 VarRefDestroy(base_ref);
443 }
444 }
445
446
447 DataType required_datatype = DataTypeFromString(opts.cp_save->lval);
448 if (rval.type != DataTypeToRvalType(required_datatype))
449 {
450 char *ref_str = VarRefToString(ref, true);
451 char *value_str = RvalToString(rval);
452 Log(LOG_LEVEL_ERR, "Variable '%s' expected a variable of type '%s', but was given incompatible value '%s'",
453 ref_str, DataTypeToString(required_datatype), value_str);
454 PromiseRef(LOG_LEVEL_ERR, pp);
455
456 free(ref_str);
457 free(value_str);
458 VarRefDestroy(ref);
459 RvalDestroy(rval);
460 return PROMISE_RESULT_FAIL;
461 }
462
463 StringSet *tags = StringSetNew();
464 StringSetAdd(tags, xstrdup("source=promise"));
465
466 Rlist *promise_meta = PromiseGetConstraintAsList(ctx, "meta", pp);
467 if (promise_meta)
468 {
469 Buffer *print;
470 for (const Rlist *rp = promise_meta; rp; rp = rp->next)
471 {
472 StringSetAdd(tags, xstrdup(RlistScalarValue(rp)));
473 if (WouldLog(LOG_LEVEL_DEBUG))
474 {
475 print = StringSetToBuffer(tags, ',');
476 Log(LOG_LEVEL_DEBUG,
477 "Added tag %s to variable %s tags (now [%s])",
478 RlistScalarValue(rp), pp->promiser, BufferData(print));
479 BufferDestroy(print);
480 }
481 }
482 }
483
484 const char *comment = PromiseGetConstraintAsRval(pp, "comment", RVAL_TYPE_SCALAR);
485
486 /* WRITE THE VARIABLE AT LAST. */
487 bool success = EvalContextVariablePutTagsSetWithComment(ctx, ref, rval.item, required_datatype,
488 tags, comment);
489 if (success && (comment != NULL))
490 {
491 Log(LOG_LEVEL_VERBOSE, "Added variable '%s' with comment '%s'",
492 pp->promiser, comment);
493 }
494
495 if (!success)
496 {
497 Log(LOG_LEVEL_VERBOSE,
498 "Unable to converge %s.%s value (possibly empty or infinite regression)",
499 ref->scope, pp->promiser);
500 PromiseRef(LOG_LEVEL_VERBOSE, pp);
501
502 VarRefDestroy(ref);
503 RvalDestroy(rval);
504 StringSetDestroy(tags);
505 return PROMISE_RESULT_FAIL;
506 }
507
508 result = PROMISE_RESULT_NOOP;
509 }
510 else
511 {
512 Log(LOG_LEVEL_ERR, "Variable %s has no promised value", pp->promiser);
513 Log(LOG_LEVEL_ERR, "Rule from %s at/before line %zu", PromiseGetBundle(pp)->source_path, opts.cp_save->offset.line);
514 result = PROMISE_RESULT_FAIL;
515 }
516
517 /*
518 * FIXME: Variable promise are exempt from normal evaluation logic still, so
519 * they are not pushed to evaluation stack before being evaluated. Due to
520 * this reason, we cannot call cfPS here to set classes, as it will error
521 * out with ProgrammingError.
522 *
523 * In order to support 'classes' body for variables as well, we call
524 * ClassAuditLog explicitly.
525 */
526 ClassAuditLog(ctx, pp, &a, result);
527
528 VarRefDestroy(ref);
529 RvalDestroy(rval);
530
531 return result;
532 }
533
CompareRval(const void * rval1_item,RvalType rval1_type,const void * rval2_item,RvalType rval2_type)534 static bool CompareRval(const void *rval1_item, RvalType rval1_type,
535 const void *rval2_item, RvalType rval2_type)
536 {
537 if (rval1_type != rval2_type)
538 {
539 return false;
540 }
541
542 switch (rval1_type)
543 {
544 case RVAL_TYPE_SCALAR:
545
546 if (IsCf3VarString(rval1_item) || IsCf3VarString(rval2_item))
547 {
548 return false; // inconclusive
549 }
550
551 if (strcmp(rval1_item, rval2_item) != 0)
552 {
553 return false;
554 }
555
556 break;
557
558 case RVAL_TYPE_LIST:
559 return RlistEqual(rval1_item, rval2_item);
560
561 case RVAL_TYPE_FNCALL:
562 return false;
563
564 default:
565 return false;
566 }
567
568 return true;
569 }
570
Epimenides(EvalContext * ctx,const char * ns,const char * scope,const char * var,Rval rval,int level)571 static bool Epimenides(EvalContext *ctx, const char *ns, const char *scope, const char *var, Rval rval, int level)
572 {
573 switch (rval.type)
574 {
575 case RVAL_TYPE_SCALAR:
576
577 if (StringContainsVar(RvalScalarValue(rval), var))
578 {
579 Log(LOG_LEVEL_ERR, "Scalar variable '%s' contains itself (non-convergent) '%s'", var, RvalScalarValue(rval));
580 return true;
581 }
582
583 if (IsCf3VarString(RvalScalarValue(rval)))
584 {
585 Buffer *exp = BufferNew();
586 ExpandScalar(ctx, ns, scope, RvalScalarValue(rval), exp);
587
588 if (strcmp(BufferData(exp), RvalScalarValue(rval)) == 0)
589 {
590 BufferDestroy(exp);
591 return false;
592 }
593
594 if (level > 3)
595 {
596 BufferDestroy(exp);
597 return false;
598 }
599
600 if (Epimenides(ctx, ns, scope, var, (Rval) { BufferGet(exp), RVAL_TYPE_SCALAR}, level + 1))
601 {
602 BufferDestroy(exp);
603 return true;
604 }
605
606 BufferDestroy(exp);
607 }
608
609 break;
610
611 case RVAL_TYPE_LIST:
612 for (const Rlist *rp = RvalRlistValue(rval); rp != NULL; rp = rp->next)
613 {
614 if (Epimenides(ctx, ns, scope, var, rp->val, level))
615 {
616 return true;
617 }
618 }
619 break;
620
621 case RVAL_TYPE_CONTAINER:
622 case RVAL_TYPE_FNCALL:
623 case RVAL_TYPE_NOPROMISEE:
624 return false;
625 }
626
627 return false;
628 }
629
630 /**
631 * @brief Collects variable constraints controlling how the promise should be converged
632 */
CollectConvergeVariableOptions(EvalContext * ctx,const Promise * pp)633 static ConvergeVariableOptions CollectConvergeVariableOptions(EvalContext *ctx, const Promise *pp)
634 {
635 ConvergeVariableOptions opts;
636 opts.drop_undefined = false;
637 opts.cp_save = NULL; /* main variable value */
638 /* By default allow variable redefinition, use "policy" constraint
639 * to override. */
640 opts.ok_redefine = true;
641 /* Main return value: becomes true at the end of the function. */
642 opts.should_converge = false;
643
644 if (!IsDefinedClass(ctx, pp->classes))
645 {
646 return opts;
647 }
648
649 int num_values = 0;
650 for (size_t i = 0; i < SeqLength(pp->conlist); i++)
651 {
652 Constraint *cp = SeqAt(pp->conlist, i);
653
654 if (strcmp(cp->lval, "comment") == 0)
655 {
656 // Comments don't affect convergence
657 // Unclear why this is in the constraint list in the first place?
658 continue;
659 }
660 else if (cp->rval.item == NULL && cp->rval.type != RVAL_TYPE_LIST)
661 {
662 // No right value, considered empty
663 continue;
664 }
665 else if (strcmp(cp->lval, "ifvarclass") == 0 ||
666 strcmp(cp->lval, "if") == 0)
667 {
668 switch (cp->rval.type)
669 {
670 case RVAL_TYPE_SCALAR:
671 if (!IsDefinedClass(ctx, cp->rval.item))
672 {
673 return opts;
674 }
675
676 break;
677
678 case RVAL_TYPE_FNCALL:
679 {
680 bool excluded = false;
681
682 /* eval it: e.g. ifvarclass => not("a_class") */
683
684 Rval res = FnCallEvaluate(ctx, PromiseGetPolicy(pp), cp->rval.item, pp).rval;
685
686 /* Don't continue unless function was evaluated properly */
687 if (res.type != RVAL_TYPE_SCALAR)
688 {
689 RvalDestroy(res);
690 return opts;
691 }
692
693 excluded = !IsDefinedClass(ctx, res.item);
694
695 RvalDestroy(res);
696
697 if (excluded)
698 {
699 return opts;
700 }
701 }
702 break;
703
704 default:
705 Log(LOG_LEVEL_ERR, "Invalid if/ifvarclass type '%c': should be string or function", cp->rval.type);
706 }
707 }
708 else if (strcmp(cp->lval, "policy") == 0)
709 {
710 if (strcmp(cp->rval.item, "ifdefined") == 0)
711 {
712 opts.drop_undefined = true;
713 }
714 else if (strcmp(cp->rval.item, "constant") == 0)
715 {
716 opts.ok_redefine = false;
717 }
718 }
719 else if (DataTypeFromString(cp->lval) != CF_DATA_TYPE_NONE)
720 {
721 num_values++;
722 opts.cp_save = cp;
723 }
724 }
725
726 if (opts.cp_save == NULL)
727 {
728 Log(LOG_LEVEL_WARNING, "Incomplete vars promise: %s",
729 pp->promiser);
730 PromiseRef(LOG_LEVEL_INFO, pp);
731 return opts;
732 }
733
734 if (num_values > 2)
735 {
736 Log(LOG_LEVEL_ERR,
737 "Variable '%s' breaks its own promise with multiple (%d) values",
738 pp->promiser, num_values);
739 PromiseRef(LOG_LEVEL_ERR, pp);
740 return opts;
741 }
742
743 /* All constraints look OK, and classes are defined. Move forward with
744 * this promise. */
745 opts.should_converge = true;
746
747 return opts;
748 }
749