1 /*
2 Copyright 2020 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 <promises.h>
26
27 #include <policy.h>
28 #include <syntax.h>
29 #include <expand.h>
30 #include <files_names.h>
31 #include <scope.h>
32 #include <vars.h>
33 #include <locks.h>
34 #include <misc_lib.h>
35 #include <fncall.h>
36 #include <eval_context.h>
37 #include <string_lib.h>
38 #include <audit.h>
39
40 static void AddDefaultBodiesToPromise(EvalContext *ctx, Promise *promise, const PromiseTypeSyntax *syntax);
41
CopyBodyConstraintsToPromise(EvalContext * ctx,Promise * pp,const Body * bp)42 void CopyBodyConstraintsToPromise(EvalContext *ctx, Promise *pp,
43 const Body *bp)
44 {
45 for (size_t k = 0; k < SeqLength(bp->conlist); k++)
46 {
47 Constraint *scp = SeqAt(bp->conlist, k);
48
49 if (IsDefinedClass(ctx, scp->classes))
50 {
51 Rval returnval = ExpandPrivateRval(ctx, NULL, "body",
52 scp->rval.item, scp->rval.type);
53 PromiseAppendConstraint(pp, scp->lval, returnval, false);
54 }
55 }
56 }
57
58 /**
59 * Get a map that rewrites body according to parameters.
60 *
61 * @NOTE make sure you free the returned map with JsonDestroy().
62 */
GetBodyRewriter(const EvalContext * ctx,const Body * current_body,const Rval * called_rval,bool in_inheritance_chain)63 static JsonElement *GetBodyRewriter(const EvalContext *ctx,
64 const Body *current_body,
65 const Rval *called_rval,
66 bool in_inheritance_chain)
67 {
68 size_t given_args = 0;
69 JsonElement *arg_rewriter = JsonObjectCreate(2);
70
71 if (called_rval == NULL)
72 {
73 // nothing needed, this is not an inherit_from rval
74 }
75 else if (called_rval->type == RVAL_TYPE_SCALAR)
76 {
77 // We leave the parameters as they were.
78
79 // Unless the current body matches the
80 // parameters of the inherited body, there
81 // will be unexpanded variables. But the
82 // alternative is to match up body and fncall
83 // arguments, which is not trivial.
84 }
85 else if (called_rval->type == RVAL_TYPE_FNCALL)
86 {
87 const Rlist *call_args = RvalFnCallValue(*called_rval)->args;
88 const Rlist *body_args = current_body->args;
89
90 given_args = RlistLen(call_args);
91
92 while (call_args != NULL &&
93 body_args != NULL)
94 {
95 JsonObjectAppendString(arg_rewriter,
96 RlistScalarValue(body_args),
97 RlistScalarValue(call_args));
98 call_args = call_args->next;
99 body_args = body_args->next;
100 }
101 }
102
103 size_t required_args = RlistLen(current_body->args);
104 // only check arguments for inherited bodies
105 if (in_inheritance_chain && required_args != given_args)
106 {
107 FatalError(ctx,
108 "Argument count mismatch for body "
109 "(gave %zu arguments) vs. inherited body '%s:%s' "
110 "(requires %zu arguments)",
111 given_args,
112 current_body->ns, current_body->name, required_args);
113 }
114
115 return arg_rewriter;
116 }
117
118 /**
119 * Appends expanded bodies to the promise #pcopy. It expands the bodies based
120 * on arguments, inheritance, and it can optionally flatten the '@' slists and
121 * expand the variables in the body according to the EvalContext.
122 */
AppendExpandedBodies(EvalContext * ctx,Promise * pcopy,const Seq * bodies_and_args,bool flatten_slists,bool expand_body_vars)123 static void AppendExpandedBodies(EvalContext *ctx, Promise *pcopy,
124 const Seq *bodies_and_args,
125 bool flatten_slists, bool expand_body_vars)
126 {
127 size_t ba_len = SeqLength(bodies_and_args);
128
129 /* Iterate over all parent bodies, and finally over the body of the
130 * promise itself, expanding arguments. We have already reversed the Seq
131 * so we start with the most distant parent in the inheritance tree. */
132 for (size_t i = 0; i < ba_len; i += 2)
133 {
134 const Rval *called_rval = SeqAt(bodies_and_args, i);
135 const Body *current_body = SeqAt(bodies_and_args, i + 1);
136 bool in_inheritance_chain= (ba_len - i > 2);
137
138 JsonElement *arg_rewriter =
139 GetBodyRewriter(ctx, current_body, called_rval,
140 in_inheritance_chain);
141
142 size_t constraints_num = SeqLength(current_body->conlist);
143 for (size_t k = 0; k < constraints_num; k++)
144 {
145 const Constraint *scp = SeqAt(current_body->conlist, k);
146
147 // we don't copy the inherit_from attribute or associated call
148 if (strcmp("inherit_from", scp->lval) == 0)
149 {
150 continue;
151 }
152
153 if (IsDefinedClass(ctx, scp->classes))
154 {
155 /* We copy the Rval expanding all, including inherited,
156 * body arguments. */
157 Rval newrv = RvalCopyRewriter(scp->rval, arg_rewriter);
158
159 /* Expand '@' slists. */
160 if (flatten_slists && newrv.type == RVAL_TYPE_LIST)
161 {
162 RlistFlatten(ctx, (Rlist **) &newrv.item);
163 }
164
165 /* Expand body vars; note it has to happen ONLY ONCE. */
166 if (expand_body_vars)
167 {
168 Rval newrv2 = ExpandPrivateRval(ctx, NULL, "body",
169 newrv.item, newrv.type);
170 RvalDestroy(newrv);
171 newrv = newrv2;
172 }
173
174 /* PromiseAppendConstraint() overwrites existing constraints,
175 thus inheritance just works, as it correctly overwrites
176 parents' constraints. */
177 Constraint *scp_copy =
178 PromiseAppendConstraint(pcopy, scp->lval,
179 newrv, false);
180 scp_copy->offset = scp->offset;
181
182 char *rval_s = RvalToString(scp->rval);
183 char *rval_exp_s = RvalToString(scp_copy->rval);
184 Log(LOG_LEVEL_DEBUG, "DeRefCopyPromise(): "
185 "expanding constraint '%s': '%s' -> '%s'",
186 scp->lval, rval_s, rval_exp_s);
187 free(rval_exp_s);
188 free(rval_s);
189 }
190 } /* for all body constraints */
191
192 JsonDestroy(arg_rewriter);
193 }
194 }
195
196 /**
197 * Copies the promise, expanding the constraints.
198 *
199 * 1. copy the promise itself
200 * 2. copy constraints: copy the bodies expanding arguments passed
201 * (arg_rewrite), copy the bundles, copy the rest of the constraints
202 * 3. flatten '@' slists everywhere
203 * 4. handle body inheritance
204 */
DeRefCopyPromise(EvalContext * ctx,const Promise * pp)205 Promise *DeRefCopyPromise(EvalContext *ctx, const Promise *pp)
206 {
207 Log(LOG_LEVEL_DEBUG,
208 "DeRefCopyPromise(): promiser:'%s'",
209 SAFENULL(pp->promiser));
210
211 Promise *pcopy = xcalloc(1, sizeof(Promise));
212
213 if (pp->promiser)
214 {
215 pcopy->promiser = xstrdup(pp->promiser);
216 }
217
218 /* Copy promisee (if not NULL) while expanding '@' slists. */
219 pcopy->promisee = RvalCopy(pp->promisee);
220 if (pcopy->promisee.type == RVAL_TYPE_LIST)
221 {
222 RlistFlatten(ctx, (Rlist **) &pcopy->promisee.item);
223 }
224
225 if (pp->promisee.item != NULL)
226 {
227 char *promisee_string = RvalToString(pp->promisee);
228
229 CF_ASSERT(pcopy->promisee.item != NULL,
230 "DeRefCopyPromise: Failed to copy promisee: %s",
231 promisee_string);
232 Log(LOG_LEVEL_DEBUG, "DeRefCopyPromise(): "
233 "expanded promisee: '%s'",
234 promisee_string);
235 free(promisee_string);
236 }
237
238 assert(pp->classes);
239 pcopy->classes = xstrdup(pp->classes);
240 pcopy->parent_section = pp->parent_section;
241 pcopy->offset.line = pp->offset.line;
242 pcopy->comment = pp->comment ? xstrdup(pp->comment) : NULL;
243 pcopy->conlist = SeqNew(10, ConstraintDestroy);
244 pcopy->org_pp = pp->org_pp;
245 pcopy->offset = pp->offset;
246
247 /* No further type checking should be necessary here, already done by CheckConstraintTypeMatch */
248
249 for (size_t i = 0; i < SeqLength(pp->conlist); i++)
250 {
251 Constraint *cp = SeqAt(pp->conlist, i);
252 const Policy *policy = PolicyFromPromise(pp);
253
254 /* bodies_and_args: Do we have body to expand, possibly with arguments?
255 * At position 0 we'll have the body, then its rval, then the same for
256 * each of its inherit_from parents. */
257 Seq *bodies_and_args = NULL;
258 const Rlist *args = NULL;
259 const char *body_reference = NULL;
260
261 /* A body template reference could look like a scalar or fn to the parser w/w () */
262 switch (cp->rval.type)
263 {
264 case RVAL_TYPE_SCALAR:
265 if (cp->references_body)
266 {
267 body_reference = RvalScalarValue(cp->rval);
268 bodies_and_args = EvalContextResolveBodyExpression(ctx, policy, body_reference, cp->lval);
269 }
270 args = NULL;
271 break;
272 case RVAL_TYPE_FNCALL:
273 body_reference = RvalFnCallValue(cp->rval)->name;
274 bodies_and_args = EvalContextResolveBodyExpression(ctx, policy, body_reference, cp->lval);
275 args = RvalFnCallValue(cp->rval)->args;
276 break;
277 default:
278 break;
279 }
280
281 /* First case is: we have a body to expand lval = body(args). */
282
283 if (bodies_and_args != NULL &&
284 SeqLength(bodies_and_args) > 0)
285 {
286 const Body *bp = SeqAt(bodies_and_args, 0);
287 assert(bp != NULL);
288
289 SeqReverse(bodies_and_args); // when we iterate, start with the furthest parent
290
291 EvalContextStackPushBodyFrame(ctx, pcopy, bp, args);
292
293 if (strcmp(bp->type, cp->lval) != 0)
294 {
295 Log(LOG_LEVEL_ERR,
296 "Body type mismatch for body reference '%s' in promise "
297 "at line %zu of file '%s', '%s' does not equal '%s'",
298 body_reference, pp->offset.line,
299 PromiseGetBundle(pp)->source_path, bp->type, cp->lval);
300 }
301
302 Log(LOG_LEVEL_DEBUG, "DeRefCopyPromise(): "
303 "copying body %s: '%s'",
304 cp->lval, body_reference);
305
306 if (IsDefinedClass(ctx, cp->classes))
307 {
308 /* For new package promises we need to have name of the
309 * package_manager body. */
310 char body_name[strlen(cp->lval) + 6];
311 xsnprintf(body_name, sizeof(body_name), "%s_name", cp->lval);
312 PromiseAppendConstraint(pcopy, body_name,
313 (Rval) {xstrdup(bp->name), RVAL_TYPE_SCALAR }, false);
314
315 /* Keep the referent body type as a boolean for convenience
316 * when checking later. */
317 PromiseAppendConstraint(pcopy, cp->lval,
318 (Rval) {xstrdup("true"), RVAL_TYPE_SCALAR }, false);
319 }
320
321 if (bp->args) /* There are arguments to insert */
322 {
323 if (!args)
324 {
325 Log(LOG_LEVEL_ERR,
326 "Argument mismatch for body reference '%s' in promise "
327 "at line %zu of file '%s'",
328 body_reference, pp->offset.line,
329 PromiseGetBundle(pp)->source_path);
330 }
331
332 AppendExpandedBodies(ctx, pcopy, bodies_and_args,
333 false, true);
334 }
335 else /* No body arguments or body undeclared */
336 {
337 if (args) /* body undeclared */
338 {
339 Log(LOG_LEVEL_ERR,
340 "Apparent body '%s' was undeclared or could "
341 "have incorrect args, but used in a promise near "
342 "line %zu of %s (possible unquoted literal value)",
343 RvalScalarValue(cp->rval), pp->offset.line,
344 PromiseGetBundle(pp)->source_path);
345 }
346 else /* no body arguments, but maybe the inherited bodies have */
347 {
348 AppendExpandedBodies(ctx, pcopy, bodies_and_args,
349 true, false);
350 }
351 }
352
353 EvalContextStackPopFrame(ctx);
354 SeqDestroy(bodies_and_args);
355 }
356 else /* constraint is not a body */
357 {
358 if (cp->references_body)
359 {
360 // assume this is a typed bundle (e.g. edit_line)
361 const Bundle *callee =
362 EvalContextResolveBundleExpression(ctx, policy,
363 body_reference,
364 cp->lval);
365 if (!callee)
366 {
367 // otherwise, assume this is a method-type call
368 callee = EvalContextResolveBundleExpression(ctx, policy,
369 body_reference,
370 "agent");
371 if (!callee)
372 {
373 callee = EvalContextResolveBundleExpression(ctx, policy,
374 body_reference,
375 "common");
376 }
377 }
378
379 if (callee == NULL &&
380 cp->rval.type != RVAL_TYPE_FNCALL &&
381 strcmp("ifvarclass", cp->lval) != 0 &&
382 strcmp("if", cp->lval) != 0)
383 {
384 char *rval_string = RvalToString(cp->rval);
385 Log(LOG_LEVEL_ERR,
386 "Apparent bundle '%s' was undeclared, but "
387 "used in a promise near line %zu of %s "
388 "(possible unquoted literal value)",
389 rval_string, pp->offset.line,
390 PromiseGetBundle(pp)->source_path);
391 free(rval_string);
392 }
393
394 Log(LOG_LEVEL_DEBUG,
395 "DeRefCopyPromise(): copying bundle: '%s'",
396 body_reference);
397 }
398 else
399 {
400 Log(LOG_LEVEL_DEBUG,
401 "DeRefCopyPromise(): copying constraint: '%s'",
402 cp->lval);
403 }
404
405 /* For all non-body constraints: copy the Rval expanding the
406 * '@' list variables. */
407
408 if (IsDefinedClass(ctx, cp->classes))
409 {
410 Rval newrv = RvalCopy(cp->rval);
411 if (newrv.type == RVAL_TYPE_LIST)
412 {
413 RlistFlatten(ctx, (Rlist **) &newrv.item);
414 }
415
416 PromiseAppendConstraint(pcopy, cp->lval, newrv, false);
417 }
418 }
419
420 } /* for all constraints */
421
422 // Add default body for promise body types that are not present
423 char *bundle_type = pcopy->parent_section->parent_bundle->type;
424 char *promise_type = pcopy->parent_section->promise_type;
425 const PromiseTypeSyntax *syntax = PromiseTypeSyntaxGet(bundle_type, promise_type);
426 AddDefaultBodiesToPromise(ctx, pcopy, syntax);
427
428 // Add default body for global body types that are not present
429 const PromiseTypeSyntax *global_syntax = PromiseTypeSyntaxGet("*", "*");
430 AddDefaultBodiesToPromise(ctx, pcopy, global_syntax);
431
432 return pcopy;
433 }
434
435 // Try to add default bodies to promise for every body type found in syntax
AddDefaultBodiesToPromise(EvalContext * ctx,Promise * promise,const PromiseTypeSyntax * syntax)436 static void AddDefaultBodiesToPromise(EvalContext *ctx, Promise *promise, const PromiseTypeSyntax *syntax)
437 {
438 // do nothing if syntax is not defined
439 if (syntax == NULL) {
440 return;
441 }
442
443 // iterate over possible constraints
444 for (int i = 0; syntax->constraints[i].lval; i++)
445 {
446 // of type body
447 if(syntax->constraints[i].dtype == CF_DATA_TYPE_BODY) {
448 const char *constraint_type = syntax->constraints[i].lval;
449 // if there is no matching body in this promise
450 if(!PromiseBundleOrBodyConstraintExists(ctx, constraint_type, promise)) {
451 const Policy *policy = PolicyFromPromise(promise);
452 // default format is <promise_type>_<body_type>
453 char* default_body_name = StringConcatenate(3, promise->parent_section->promise_type, "_", constraint_type);
454 const Body *bp = EvalContextFindFirstMatchingBody(policy, constraint_type, "bodydefault", default_body_name);
455 if(bp) {
456 Log(LOG_LEVEL_VERBOSE, "Using the default body: %60s", default_body_name);
457 CopyBodyConstraintsToPromise(ctx, promise, bp);
458 }
459 free(default_body_name);
460 }
461 }
462 }
463 }
464
465 /*****************************************************************************/
466
EvaluateConstraintIteration(EvalContext * ctx,const Constraint * cp,Rval * rval_out)467 static bool EvaluateConstraintIteration(EvalContext *ctx, const Constraint *cp, Rval *rval_out)
468 {
469 assert(cp->type == POLICY_ELEMENT_TYPE_PROMISE);
470 const Promise *pp = cp->parent.promise;
471
472 if (!IsDefinedClass(ctx, cp->classes))
473 {
474 return false;
475 }
476
477 if (ExpectedDataType(cp->lval) == CF_DATA_TYPE_BUNDLE)
478 {
479 *rval_out = ExpandBundleReference(ctx, NULL, "this", cp->rval);
480 }
481 else
482 {
483 *rval_out = EvaluateFinalRval(ctx, PromiseGetPolicy(pp), NULL,
484 "this", cp->rval, false, pp);
485 }
486
487 return true;
488 }
489
490 /**
491 @brief Helper function to determine whether the Rval of ifvarclass/if/unless is defined.
492 If the Rval is a function, call that function.
493 */
CheckVarClassExpression(const EvalContext * ctx,const Constraint * cp,Promise * pcopy)494 static ExpressionValue CheckVarClassExpression(const EvalContext *ctx, const Constraint *cp, Promise *pcopy)
495 {
496 assert(ctx);
497 assert(cp);
498 assert(pcopy);
499
500 /*
501 This might fail to expand if there are unexpanded variables in function arguments
502 (in which case the function won't be called at all), but the function still returns true.
503
504 If expansion fails for other reasons, assume that we don't know this class.
505 */
506 Rval final;
507 if (!EvaluateConstraintIteration((EvalContext*)ctx, cp, &final))
508 {
509 return EXPRESSION_VALUE_ERROR;
510 }
511
512 char *classes = NULL;
513 PromiseAppendConstraint(pcopy, cp->lval, final, false);
514 switch (final.type)
515 {
516 case RVAL_TYPE_SCALAR:
517 classes = RvalScalarValue(final);
518 break;
519
520 case RVAL_TYPE_FNCALL:
521 Log(LOG_LEVEL_DEBUG, "Function call in class expression did not succeed");
522 break;
523
524 default:
525 break;
526 }
527
528 if (classes == NULL)
529 {
530 return EXPRESSION_VALUE_ERROR;
531 }
532 // sanity check for unexpanded variables
533 if (strchr(classes, '$') || strchr(classes, '@'))
534 {
535 Log(LOG_LEVEL_DEBUG, "Class expression did not evaluate");
536 return EXPRESSION_VALUE_ERROR;
537 }
538
539 return CheckClassExpression(ctx, classes);
540 }
541
542 /* Expands "$(this.promiser)" comment if present. Writes the result to pp. */
DereferenceAndPutComment(Promise * pp,const char * comment)543 static void DereferenceAndPutComment(Promise* pp, const char *comment)
544 {
545 free(pp->comment);
546
547 char *sp;
548 if ((sp = strstr(comment, "$(this.promiser)")) != NULL ||
549 (sp = strstr(comment, "${this.promiser}")) != NULL)
550 {
551 char *s;
552 int this_len = strlen("$(this.promiser)");
553 int this_offset = sp - comment;
554 xasprintf(&s, "%.*s%s%s",
555 this_offset, comment, pp->promiser,
556 &comment[this_offset + this_len]);
557
558 pp->comment = s;
559 }
560 else
561 {
562 pp->comment = xstrdup(comment);
563 }
564 }
565
ExpandDeRefPromise(EvalContext * ctx,const Promise * pp,bool * excluded)566 Promise *ExpandDeRefPromise(EvalContext *ctx, const Promise *pp, bool *excluded)
567 {
568 assert(pp != NULL);
569 assert(pp->parent_section != NULL);
570 assert(pp->promiser != NULL);
571 assert(pp->classes != NULL);
572 assert(excluded != NULL);
573
574 *excluded = false;
575
576 Rval returnval = ExpandPrivateRval(ctx, NULL, "this", pp->promiser, RVAL_TYPE_SCALAR);
577 if (returnval.item == NULL)
578 {
579 assert(returnval.type == RVAL_TYPE_LIST ||
580 returnval.type == RVAL_TYPE_NOPROMISEE);
581 /* TODO Log() empty slist, promise skipped? */
582 *excluded = true;
583 return NULL;
584 }
585 Promise *pcopy = xcalloc(1, sizeof(Promise));
586 pcopy->promiser = RvalScalarValue(returnval);
587
588 /* TODO remove the conditions here for fixing redmine#7880. */
589 if ((strcmp("files", pp->parent_section->promise_type) != 0) &&
590 (strcmp("storage", pp->parent_section->promise_type) != 0))
591 {
592 EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "promiser", pcopy->promiser,
593 CF_DATA_TYPE_STRING, "source=promise");
594 }
595
596 if (pp->promisee.item)
597 {
598 pcopy->promisee = EvaluateFinalRval(ctx, PromiseGetPolicy(pp), NULL, "this", pp->promisee, true, pp);
599 }
600 else
601 {
602 pcopy->promisee = (Rval) {NULL, RVAL_TYPE_NOPROMISEE };
603 }
604
605 pcopy->classes = xstrdup(pp->classes);
606 pcopy->parent_section = pp->parent_section;
607 pcopy->offset.line = pp->offset.line;
608 pcopy->comment = pp->comment ? xstrdup(pp->comment) : NULL;
609 pcopy->conlist = SeqNew(10, ConstraintDestroy);
610 pcopy->org_pp = pp->org_pp;
611
612 // if this is a class promise, check if it is already set, if so, skip
613 if (strcmp("classes", pp->parent_section->promise_type) == 0)
614 {
615 if (IsDefinedClass(ctx, CanonifyName(pcopy->promiser)))
616 {
617 Log(LOG_LEVEL_DEBUG,
618 "Skipping evaluation of classes promise as class '%s' is already set",
619 CanonifyName(pcopy->promiser));
620
621 *excluded = true;
622 return pcopy;
623 }
624 }
625
626 /* Look for 'if'/'ifvarclass' exclusion. */
627 {
628 /* We need to make sure to check both 'if' and 'ifvarclass' constraints. */
629 bool checked_if = false;
630 const Constraint *ifvarclass = PromiseGetConstraint(pp, "ifvarclass");
631 if (!ifvarclass)
632 {
633 ifvarclass = PromiseGetConstraint(pp, "if");
634 checked_if = true;
635 }
636
637 // if - Skip if false or error:
638 while (ifvarclass != NULL)
639 {
640 if (CheckVarClassExpression(ctx, ifvarclass, pcopy) != EXPRESSION_VALUE_TRUE)
641 {
642 if (LogGetGlobalLevel() >= LOG_LEVEL_VERBOSE)
643 {
644 char *ifvarclass_string = RvalToString(ifvarclass->rval);
645 Log(LOG_LEVEL_VERBOSE, "Skipping promise '%s'"
646 " because constraint '%s => %s' is not met",
647 pp->promiser, ifvarclass->lval, ifvarclass_string);
648 free(ifvarclass_string);
649 }
650 *excluded = true;
651 return pcopy;
652 }
653 if (!checked_if)
654 {
655 ifvarclass = PromiseGetConstraint(pp, "if");
656 checked_if = true;
657 }
658 else
659 {
660 ifvarclass = NULL;
661 }
662 }
663 }
664
665 /* Look for 'unless' exclusion. */
666 {
667 const Constraint *unless = PromiseGetConstraint(pp, "unless");
668
669 // unless - Skip if true or error:
670 if (unless != NULL)
671 {
672 // If the rval is a function, CheckVarClassExpression will call it
673 // It will evaluate the class expression as well:
674 const ExpressionValue value = CheckVarClassExpression(ctx, unless, pcopy);
675 // If it returns EXPRESSION_VALUE_ERROR, it most likely means there
676 // are unexpanded variables in the rval (possibly in function calls)
677
678 if ((EvalContextGetPass(ctx) == CF_DONEPASSES-1)
679 && (value == EXPRESSION_VALUE_ERROR))
680 {
681 char *unless_string = RvalToString(unless->rval);
682 // The rval is most likely a string or a function call,
683 // with unexpanded variables, for example:
684 // unless => "$(no_such_var)"
685 // We default to NOT skipping (since if would skip).
686 Log(LOG_LEVEL_VERBOSE,
687 "Not skipping %s promise '%s' with constraint '%s => %s' in last evaluation pass (since if would skip)",
688 pp->parent_section->promise_type,
689 pp->promiser,
690 unless->lval,
691 unless_string);
692 free(unless_string);
693 }
694 else if (value != EXPRESSION_VALUE_FALSE)
695 {
696 if (LogGetGlobalLevel() >= LOG_LEVEL_VERBOSE)
697 {
698 char *unless_string = RvalToString(unless->rval);
699 Log(LOG_LEVEL_VERBOSE,
700 "Skipping promise '%s' because constraint '%s => %s' is not met",
701 pp->promiser, unless->lval, unless_string);
702 free(unless_string);
703 }
704 *excluded = true;
705 return pcopy;
706 }
707 }
708 }
709
710 /* Look for depends_on exclusion. */
711 {
712 const Constraint *depends_on = PromiseGetConstraint(pp, "depends_on");
713 if (depends_on)
714 {
715 Rval final;
716 if (EvaluateConstraintIteration(ctx, depends_on, &final))
717 {
718 PromiseAppendConstraint(pcopy, depends_on->lval, final, false);
719
720 if (MissingDependencies(ctx, pcopy))
721 {
722 *excluded = true;
723 return pcopy;
724 }
725 }
726 }
727 }
728
729 /* Evaluate all constraints. */
730 for (size_t i = 0; i < SeqLength(pp->conlist); i++)
731 {
732 Constraint *cp = SeqAt(pp->conlist, i);
733
734 // special constraints ifvarclass and depends_on are evaluated before the rest of the constraints
735 if (strcmp(cp->lval, "ifvarclass") == 0 ||
736 strcmp(cp->lval, "if") == 0 ||
737 strcmp(cp->lval, "unless") == 0 ||
738 strcmp(cp->lval, "depends_on") == 0)
739 {
740 continue;
741 }
742
743 Rval final;
744 if (!EvaluateConstraintIteration(ctx, cp, &final))
745 {
746 continue;
747 }
748
749 PromiseAppendConstraint(pcopy, cp->lval, final, false);
750
751 if (strcmp(cp->lval, "comment") == 0)
752 {
753 if (final.type != RVAL_TYPE_SCALAR)
754 {
755 Log(LOG_LEVEL_ERR, "Comments can only be scalar objects, not '%s' in '%s'",
756 RvalTypeToString(final.type), pp->promiser);
757 }
758 else
759 {
760 assert(final.item != NULL); /* it's SCALAR type */
761 DereferenceAndPutComment(pcopy, final.item);
762 }
763 }
764 }
765
766 return pcopy;
767 }
768
PromiseRef(LogLevel level,const Promise * pp)769 void PromiseRef(LogLevel level, const Promise *pp)
770 {
771 if (pp == NULL)
772 {
773 return;
774 }
775
776 if (PromiseGetBundle(pp)->source_path)
777 {
778 Log(level, "Promise belongs to bundle '%s' in file '%s' near line %zu", PromiseGetBundle(pp)->name,
779 PromiseGetBundle(pp)->source_path, pp->offset.line);
780 }
781 else
782 {
783 Log(level, "Promise belongs to bundle '%s' near line %zu", PromiseGetBundle(pp)->name,
784 pp->offset.line);
785 }
786
787 if (pp->comment)
788 {
789 Log(level, "Comment is '%s'", pp->comment);
790 }
791
792 switch (pp->promisee.type)
793 {
794 case RVAL_TYPE_SCALAR:
795 Log(level, "This was a promise to '%s'", (char *)(pp->promisee.item));
796 break;
797 case RVAL_TYPE_LIST:
798 {
799 Writer *w = StringWriter();
800 RlistWrite(w, pp->promisee.item);
801 char *p = StringWriterClose(w);
802 Log(level, "This was a promise to '%s'", p);
803 free(p);
804 break;
805 }
806 default:
807 break;
808 }
809 }
810
811 /*******************************************************************/
812
813 /* Old legacy function from Enterprise, TODO remove static string. */
PromiseID(const Promise * pp)814 const char *PromiseID(const Promise *pp)
815 {
816 static char id[CF_MAXVARSIZE];
817 char vbuff[CF_MAXVARSIZE];
818 const char *handle = PromiseGetHandle(pp);
819
820 if (handle)
821 {
822 snprintf(id, CF_MAXVARSIZE, "%s", CanonifyName(handle));
823 }
824 else if (pp && PromiseGetBundle(pp)->source_path)
825 {
826 snprintf(vbuff, CF_MAXVARSIZE, "%s", ReadLastNode(PromiseGetBundle(pp)->source_path));
827 snprintf(id, CF_MAXVARSIZE, "promise_%s_%zu", CanonifyName(vbuff), pp->offset.line);
828 }
829 else
830 {
831 snprintf(id, CF_MAXVARSIZE, "unlabelled_promise");
832 }
833
834 return id;
835 }
836