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 <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     const char *promise_type = PromiseGetPromiseType(pcopy);
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, PromiseGetPromiseType(promise), "_", 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", PromiseGetPromiseType(pp)) != 0) &&
590         (strcmp("storage", PromiseGetPromiseType(pp)) != 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", PromiseGetPromiseType(pp)) == 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                     PromiseGetPromiseType(pp),
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