"Fossies" - the Fresh Open Source Software Archive

Member "cfengine-3.15.4/libpromises/expand.c" (7 Jun 2021, 41459 Bytes) of package /linux/misc/cfengine-3.15.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 /*
    2   Copyright 2019 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 <expand.h>
   26 
   27 #include <misc_lib.h>
   28 #include <eval_context.h>
   29 #include <policy.h>
   30 #include <promises.h>
   31 #include <vars.h>
   32 #include <syntax.h>
   33 #include <files_names.h>
   34 #include <scope.h>
   35 #include <matching.h>
   36 #include <unix.h>
   37 #include <attributes.h>
   38 #include <fncall.h>
   39 #include <iteration.h>
   40 #include <audit.h>
   41 #include <verify_vars.h>
   42 #include <string_lib.h>
   43 #include <conversion.h>
   44 #include <verify_classes.h>
   45 
   46 /**
   47  * VARIABLES AND PROMISE EXPANSION
   48  *
   49  * Expanding variables is easy -- expanding lists automagically requires
   50  * some thought. Remember that
   51  *
   52  * promiser <=> RVAL_TYPE_SCALAR
   53  * promisee <=> RVAL_TYPE_LIST
   54  *
   55  * For bodies we have
   56  *
   57  * lval <=> RVAL_TYPE_LIST | RVAL_TYPE_SCALAR
   58  *
   59  * Any list or container variable occurring within a scalar or in place of a
   60  * scalar is assumed to be iterated i.e. $(name). See comments in iteration.c.
   61  *
   62  * Any list variable @(name) is *not iterated*, but dropped into place (see
   63  * DeRefCopyPromise()).
   64  *
   65  * Please note that bodies cannot contain iterators.
   66  *
   67  * The full process of promise and variable expansion is mostly outlined in
   68  * ExpandPromise() and ExpandPromiseAndDo() and the basic steps are:
   69  *
   70  * + Skip everything if the class guard is not defined.
   71  *
   72  * + DeRefCopyPromise(): *Copy the promise* while expanding '@' slists and body
   73  *   arguments and handling body inheritance. This requires one round of
   74  *   expansion with scopeid "body".
   75  *
   76  * + Push promise frame
   77  *
   78  * + MapIteratorsFromRval(): Parse all strings (promiser-promisee-constraints),
   79  *   find all unexpanded variables, mangle them if needed (if they are
   80  *   namespaced/scoped), and *initialise the wheels* in the iteration engine
   81  *   (iterctx) to iterate over iterable variables (slists and containers). See
   82  *   comments in iteration.c for further details.
   83  *
   84  * + For every iteration:
   85  *
   86  *   - Push iteration frame
   87  *
   88  *   - EvalContextStackPushPromiseIterationFrame()->ExpandDeRefPromise(): Make
   89  *     another copy of the promise with all constraints evaluated and variables
   90  *     expanded.
   91  *
   92  *     -- NOTE: As a result all *functions are also evaluated*, even if they are
   93  *        not to be used immediately (for example promises that the actuator skips
   94  *        because of ifvarclass, see promises.c:ExpandDeRefPromise() ).
   95  *
   96  *        -- (TODO IS IT CORRECT?) In a sub-bundle, create a new context and make
   97  *           hashes of the the transferred variables in the temporary context
   98  *
   99  *   - Run the actuator (=act_on_promise= i.e. =VerifyWhateverPromise()=)
  100  *
  101  *   - Pop iteration frame
  102  *
  103  * + Pop promise frame
  104  *
  105  */
  106 
  107 static inline char opposite(char c);
  108 
  109 static void PutHandleVariable(EvalContext *ctx, const Promise *pp)
  110 {
  111     char *handle_s;
  112     const char *existing_handle = PromiseGetHandle(pp);
  113 
  114     if (existing_handle != NULL)
  115     {
  116         // This ordering is necessary to get automated canonification
  117         handle_s = ExpandScalar(ctx, NULL, "this", existing_handle, NULL);
  118         CanonifyNameInPlace(handle_s);
  119     }
  120     else
  121     {
  122         handle_s = xstrdup(PromiseID(pp));                /* default handle */
  123     }
  124 
  125     EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS,
  126                                   "handle", handle_s,
  127                                   CF_DATA_TYPE_STRING, "source=promise");
  128     free(handle_s);
  129 }
  130 
  131 /**
  132  * Recursively go down the #rval and run PromiseIteratorPrepare() to take note
  133  * of all iterables and mangle all rvals than need to be mangled before
  134  * iterating.
  135  */
  136 static void MapIteratorsFromRval(EvalContext *ctx,
  137                                  PromiseIterator *iterctx,
  138                                  Rval rval)
  139 {
  140     switch (rval.type)
  141     {
  142 
  143     case RVAL_TYPE_SCALAR:
  144         PromiseIteratorPrepare(iterctx, ctx, RvalScalarValue(rval));
  145         break;
  146 
  147     case RVAL_TYPE_LIST:
  148         for (const Rlist *rp = RvalRlistValue(rval);
  149              rp != NULL; rp = rp->next)
  150         {
  151             MapIteratorsFromRval(ctx, iterctx, rp->val);
  152         }
  153         break;
  154 
  155     case RVAL_TYPE_FNCALL:
  156     {
  157         char *fn_name = RvalFnCallValue(rval)->name;
  158 
  159         /* Check function name. */
  160         PromiseIteratorPrepare(iterctx, ctx, fn_name);
  161 
  162         /* Check each of the function arguments. */
  163         /* EXCEPT on functions that use special variables: the mangled
  164          * variables would never be resolved if they contain inner special
  165          * variables (for example "$(bundle.A[$(this.k)])" and the returned
  166          * slist would contained mangled vars like "bundle#A[1]" which would
  167          * never resolve in future iterations. By skipping the iteration
  168          * engine for now, the function returns an slist with unmangled
  169          * entries, and the iteration engine works correctly on the next
  170          * pass! */
  171         if (strcmp(fn_name, "maplist") != 0 &&
  172             strcmp(fn_name, "mapdata") != 0 &&
  173             strcmp(fn_name, "maparray")!= 0)
  174         {
  175             for (Rlist *rp = RvalFnCallValue(rval)->args;
  176                  rp != NULL;  rp = rp->next)
  177             {
  178                 MapIteratorsFromRval(ctx, iterctx, rp->val);
  179             }
  180         }
  181         break;
  182     }
  183 
  184     case RVAL_TYPE_CONTAINER:
  185     case RVAL_TYPE_NOPROMISEE:
  186         break;
  187     }
  188 }
  189 
  190 static PromiseResult ExpandPromiseAndDo(EvalContext *ctx, PromiseIterator *iterctx,
  191                                         PromiseActuator *act_on_promise, void *param,
  192                                         bool actuate_ifelse)
  193 {
  194     PromiseResult result = PROMISE_RESULT_SKIPPED;
  195 
  196     /* In the case of ifelse() we must always include an extra round of "actuation"
  197      * in the while loop below. PromiseIteratorNext() will return false in the case
  198      * that there are doubly-unresolved Rvals like $($(missing)).
  199      * We can't add an empty wheel because that is skipped as well as noted in
  200      * libpromises/iteration.c ShouldAddVariableAsIterationWheel(). */
  201     bool ifelse_actuated = !actuate_ifelse;
  202 
  203     /* TODO this loop could be completely skipped for for non vars/classes if
  204      *      act_on_promise is CommonEvalPromise(). */
  205     while (PromiseIteratorNext(iterctx, ctx) || !ifelse_actuated)
  206     {
  207         /*
  208          * ACTUAL WORK PART 1: Get a (another) copy of the promise.
  209          *
  210          * Basically this evaluates all constraints.  As a result it evaluates
  211          * all functions, even if they are not to be used immediately (for
  212          * example promises that the actuator skips because of ifvarclass).
  213          */
  214         const Promise *pexp =                           /* expanded promise */
  215             EvalContextStackPushPromiseIterationFrame(ctx, iterctx);
  216         if (pexp == NULL)                       /* is the promise excluded? */
  217         {
  218             result = PromiseResultUpdate(result, PROMISE_RESULT_SKIPPED);
  219             ifelse_actuated = true;
  220             continue;
  221         }
  222 
  223         /* ACTUAL WORK PART 2: run the actuator */
  224         PromiseResult iteration_result = act_on_promise(ctx, pexp, param);
  225 
  226         /* iteration_result is always NOOP for PRE-EVAL. */
  227         result = PromiseResultUpdate(result, iteration_result);
  228 
  229         /* Redmine#6484: Do not store promise handles during PRE-EVAL, to
  230          *               avoid package promise always running. */
  231         if (act_on_promise != &CommonEvalPromise)
  232         {
  233             NotifyDependantPromises(ctx, pexp, iteration_result);
  234         }
  235 
  236         /* EVALUATE VARS PROMISES again, allowing redefinition of
  237          * variables. The theory behind this is that the "sampling rate" of
  238          * vars promise needs to be double than the rest. */
  239         if (strcmp(pexp->parent_promise_type->name, "vars") == 0 ||
  240             strcmp(pexp->parent_promise_type->name, "meta") == 0)
  241         {
  242             if (act_on_promise != &VerifyVarPromise)
  243             {
  244                 VerifyVarPromise(ctx, pexp, NULL);
  245             }
  246         }
  247 
  248         /* Why do we push/pop an iteration frame, if all iterated variables
  249          * are Put() on the previous scope? */
  250         EvalContextStackPopFrame(ctx);
  251         ifelse_actuated = true;
  252     }
  253 
  254     return result;
  255 }
  256 
  257 PromiseResult ExpandPromise(EvalContext *ctx, const Promise *pp,
  258                             PromiseActuator *act_on_promise, void *param)
  259 {
  260     if (!IsDefinedClass(ctx, pp->classes))
  261     {
  262         return PROMISE_RESULT_SKIPPED;
  263     }
  264 
  265     /* 1. Copy the promise while expanding '@' slists and body arguments
  266      *    (including body inheritance). */
  267     Promise *pcopy = DeRefCopyPromise(ctx, pp);
  268 
  269     EvalContextStackPushPromiseFrame(ctx, pcopy);
  270     PromiseIterator *iterctx = PromiseIteratorNew(pcopy);
  271 
  272     /* 2. Parse all strings (promiser-promisee-constraints), find all
  273           unexpanded variables, mangle them if needed (if they are
  274           namespaced/scoped), and start the iteration engine (iterctx) to
  275           iterate over slists and containers. */
  276 
  277     MapIteratorsFromRval(ctx, iterctx,
  278                          (Rval) { pcopy->promiser, RVAL_TYPE_SCALAR });
  279 
  280     if (pcopy->promisee.item != NULL)
  281     {
  282         MapIteratorsFromRval(ctx, iterctx, pcopy->promisee);
  283     }
  284 
  285     bool actuate_ifelse = false;
  286     for (size_t i = 0; i < SeqLength(pcopy->conlist); i++)
  287     {
  288         Constraint *cp = SeqAt(pcopy->conlist, i);
  289         if (cp->rval.type == RVAL_TYPE_FNCALL &&
  290             strcmp(RvalFnCallValue(cp->rval)->name, "ifelse") == 0)
  291         {
  292             actuate_ifelse = true;
  293         }
  294         MapIteratorsFromRval(ctx, iterctx, cp->rval);
  295     }
  296 
  297     /* 3. GO! */
  298     PutHandleVariable(ctx, pcopy);
  299     PromiseResult result = ExpandPromiseAndDo(ctx, iterctx,
  300                                               act_on_promise, param, actuate_ifelse);
  301 
  302     EvalContextStackPopFrame(ctx);
  303     PromiseIteratorDestroy(iterctx);
  304     PromiseDestroy(pcopy);
  305 
  306     return result;
  307 }
  308 
  309 
  310 /*********************************************************************/
  311 /*********************************************************************/
  312 
  313 Rval ExpandPrivateRval(EvalContext *ctx,
  314                        const char *ns, const char *scope,
  315                        const void *rval_item, RvalType rval_type)
  316 {
  317     Rval returnval;
  318     returnval.item = NULL;
  319     returnval.type = RVAL_TYPE_NOPROMISEE;
  320 
  321     switch (rval_type)
  322     {
  323     case RVAL_TYPE_SCALAR:
  324         returnval.item = ExpandScalar(ctx, ns, scope, rval_item, NULL);
  325         returnval.type = RVAL_TYPE_SCALAR;
  326         break;
  327     case RVAL_TYPE_LIST:
  328         returnval.item = ExpandList(ctx, ns, scope, rval_item, true);
  329         returnval.type = RVAL_TYPE_LIST;
  330         break;
  331 
  332     case RVAL_TYPE_FNCALL:
  333         returnval.item = ExpandFnCall(ctx, ns, scope, rval_item);
  334         returnval.type = RVAL_TYPE_FNCALL;
  335         break;
  336 
  337     case RVAL_TYPE_CONTAINER:
  338         returnval = RvalNew(rval_item, RVAL_TYPE_CONTAINER);
  339         break;
  340 
  341     case RVAL_TYPE_NOPROMISEE:
  342         break;
  343     }
  344 
  345     return returnval;
  346 }
  347 
  348 /**
  349  * Detects a variable expansion inside of a data/list reference, for example
  350  * "@(${container_name})" or "@(prefix${container_name})" or
  351  * "@(nspace:${container_name})" or "@(container_name[${field}])".
  352  *
  353  * @note This function doesn't have to be bullet-proof, it only needs to
  354  *       properly detect valid cases. The rest is left to the parser and code
  355  *       expanding variables.
  356  */
  357 static inline bool VariableDataOrListReference(const char *str)
  358 {
  359     assert(str != NULL);
  360 
  361     size_t len = strlen(str);
  362 
  363     /* at least '@($(X))' is needed */
  364     if (len < 7)
  365     {
  366         return false;
  367     }
  368 
  369     if (!((str[0] == '@') &&
  370           ((str[1] == '{') || (str[1] == '('))))
  371     {
  372         return false;
  373     }
  374 
  375     /* Check if, after '@(', there are only
  376      *   - characters allowed in data/list names or
  377      *   - ':' to separate namespace from the name or
  378      *   - '.' to separate bundle and variable name or,
  379      *   - '[' for data/list field/index specification,
  380      * followed by "$(" or "${" with a matching close bracket somewhere. */
  381     for (size_t i = 2; i < len; i++)
  382     {
  383         if (!(isalnum((int) str[i]) || (str[i] == '_') ||
  384               (str[i] == ':') || (str[i] == '$') || (str[i] == '.') || (str[i] == '[')))
  385         {
  386             return false;
  387         }
  388 
  389         if (str[i] == '$')
  390         {
  391             if (((i + 1) < len) && ((str[i + 1] == '{') || (str[i + 1] == '(')))
  392             {
  393                 int close_bracket = (int) opposite(str[i+1]);
  394                 return (strchr(str + i + 2, close_bracket) != NULL);
  395             }
  396             else
  397             {
  398                 return false;
  399             }
  400         }
  401     }
  402 
  403     return false;
  404 }
  405 
  406 static Rval ExpandListEntry(EvalContext *ctx,
  407                             const char *ns, const char *scope,
  408                             int expandnaked, Rval entry)
  409 {
  410     Rval expanded_data_list = {0};
  411     /* If rval is something like '@($(container_name).field)', we need to expand
  412      * the nested variable first. */
  413     if (entry.type == RVAL_TYPE_SCALAR &&
  414         VariableDataOrListReference(entry.item))
  415     {
  416         entry = ExpandPrivateRval(ctx, ns, scope, entry.item, entry.type);
  417         expanded_data_list = entry;
  418     }
  419 
  420     if (entry.type == RVAL_TYPE_SCALAR &&
  421         IsNakedVar(entry.item, '@'))
  422     {
  423         if (expandnaked)
  424         {
  425             char naked[CF_MAXVARSIZE];
  426             GetNaked(naked, entry.item);
  427 
  428             if (IsExpandable(naked))
  429             {
  430                 char *exp = ExpandScalar(ctx, ns, scope, naked, NULL);
  431                 strlcpy(naked, exp, sizeof(naked));             /* TODO err */
  432                 free(exp);
  433             }
  434 
  435             /* Check again, it might have changed. */
  436             if (!IsExpandable(naked))
  437             {
  438                 VarRef *ref = VarRefParseFromScope(naked, scope);
  439 
  440                 DataType value_type;
  441                 const void *value = EvalContextVariableGet(ctx, ref, &value_type);
  442                 VarRefDestroy(ref);
  443 
  444                 if (value_type != CF_DATA_TYPE_NONE)     /* variable found? */
  445                 {
  446                     Rval ret = ExpandPrivateRval(ctx, ns, scope, value,
  447                                                  DataTypeToRvalType(value_type));
  448                     RvalDestroy(expanded_data_list);
  449                     return ret;
  450                 }
  451             }
  452         }
  453         else
  454         {
  455             Rval ret = RvalNew(entry.item, RVAL_TYPE_SCALAR);
  456             RvalDestroy(expanded_data_list);
  457             return ret;
  458         }
  459     }
  460 
  461     Rval ret = ExpandPrivateRval(ctx, ns, scope, entry.item, entry.type);
  462     RvalDestroy(expanded_data_list);
  463     return ret;
  464 }
  465 
  466 Rlist *ExpandList(EvalContext *ctx,
  467                   const char *ns, const char *scope,
  468                   const Rlist *list, int expandnaked)
  469 {
  470     Rlist *start = NULL;
  471 
  472     for (const Rlist *rp = list; rp != NULL; rp = rp->next)
  473     {
  474         Rval returnval = ExpandListEntry(ctx, ns, scope, expandnaked, rp->val);
  475         RlistAppend(&start, returnval.item, returnval.type);
  476         RvalDestroy(returnval);
  477     }
  478 
  479     return start;
  480 }
  481 
  482 /*********************************************************************/
  483 
  484 Rval ExpandBundleReference(EvalContext *ctx,
  485                            const char *ns, const char *scope,
  486                            Rval rval)
  487 {
  488     // Allocates new memory for the copy
  489     switch (rval.type)
  490     {
  491     case RVAL_TYPE_SCALAR:
  492         return (Rval) { ExpandScalar(ctx, ns, scope, RvalScalarValue(rval), NULL),
  493                         RVAL_TYPE_SCALAR };
  494 
  495     case RVAL_TYPE_FNCALL:
  496         return (Rval) { ExpandFnCall(ctx, ns, scope, RvalFnCallValue(rval)),
  497                         RVAL_TYPE_FNCALL};
  498 
  499     case RVAL_TYPE_CONTAINER:
  500     case RVAL_TYPE_LIST:
  501     case RVAL_TYPE_NOPROMISEE:
  502          return RvalNew(NULL, RVAL_TYPE_NOPROMISEE);
  503     }
  504 
  505     assert(false);
  506     return RvalNew(NULL, RVAL_TYPE_NOPROMISEE);
  507 }
  508 
  509 /**
  510  * Expand a #string into Buffer #out, returning the pointer to the string
  511  * itself, inside the Buffer #out. If #out is NULL then the buffer will be
  512  * created and destroyed internally.
  513  *
  514  * @retval NULL something went wrong
  515  */
  516 char *ExpandScalar(const EvalContext *ctx, const char *ns, const char *scope,
  517                    const char *string, Buffer *out)
  518 {
  519     bool out_belongs_to_us = false;
  520 
  521     if (out == NULL)
  522     {
  523         out               = BufferNew();
  524         out_belongs_to_us = true;
  525     }
  526 
  527     assert(string != NULL);
  528     assert(out != NULL);
  529     Buffer *current_item = BufferNew();
  530 
  531     for (const char *sp = string; *sp != '\0'; sp++)
  532     {
  533         BufferClear(current_item);
  534         ExtractScalarPrefix(current_item, sp, strlen(sp));
  535 
  536         BufferAppend(out, BufferData(current_item), BufferSize(current_item));
  537         sp += BufferSize(current_item);
  538         if (*sp == '\0')
  539         {
  540             break;
  541         }
  542 
  543         BufferClear(current_item);
  544         char varstring = sp[1];
  545         ExtractScalarReference(current_item,  sp, strlen(sp), true);
  546         sp += BufferSize(current_item) + 2;
  547 
  548         if (IsCf3VarString(BufferData(current_item)))
  549         {
  550             Buffer *temp = BufferCopy(current_item);
  551             BufferClear(current_item);
  552             ExpandScalar(ctx, ns, scope, BufferData(temp), current_item);
  553             BufferDestroy(temp);
  554         }
  555 
  556         if (!IsExpandable(BufferData(current_item)))
  557         {
  558             VarRef *ref = VarRefParseFromNamespaceAndScope(
  559                 BufferData(current_item),
  560                 ns, scope, CF_NS, '.');
  561             DataType value_type;
  562             const void *value = EvalContextVariableGet(ctx, ref, &value_type);
  563             VarRefDestroy(ref);
  564 
  565             switch (DataTypeToRvalType(value_type))
  566             {
  567             case RVAL_TYPE_SCALAR:
  568                 assert(value != NULL);
  569                 BufferAppendString(out, value);
  570                 continue;
  571                 break;
  572 
  573             case RVAL_TYPE_CONTAINER:
  574             {
  575                 assert(value != NULL);
  576                 const JsonElement *jvalue = value;      /* instead of casts */
  577                 if (JsonGetElementType(jvalue) == JSON_ELEMENT_TYPE_PRIMITIVE)
  578                 {
  579                     BufferAppendString(out, JsonPrimitiveGetAsString(jvalue));
  580                     continue;
  581                 }
  582                 break;
  583             }
  584             default:
  585                 /* TODO Log() */
  586                 break;
  587             }
  588         }
  589 
  590         if (varstring == '{')
  591         {
  592             BufferAppendF(out, "${%s}", BufferData(current_item));
  593         }
  594         else
  595         {
  596             BufferAppendF(out, "$(%s)", BufferData(current_item));
  597         }
  598     }
  599 
  600     BufferDestroy(current_item);
  601 
  602     LogDebug(LOG_MOD_EXPAND, "ExpandScalar( %s : %s . %s )  =>  %s",
  603              SAFENULL(ns), SAFENULL(scope), string, BufferData(out));
  604 
  605     return out_belongs_to_us ? BufferClose(out) : BufferGet(out);
  606 }
  607 
  608 /*********************************************************************/
  609 
  610 Rval EvaluateFinalRval(EvalContext *ctx, const Policy *policy,
  611                        const char *ns, const char *scope,
  612                        Rval rval, bool forcelist, const Promise *pp)
  613 {
  614     assert(ctx);
  615     assert(policy);
  616     Rval returnval;
  617 
  618     /* Treat lists specially. */
  619     if (rval.type == RVAL_TYPE_SCALAR && IsNakedVar(rval.item, '@'))
  620     {
  621         char naked[CF_MAXVARSIZE];
  622         GetNaked(naked, rval.item);
  623 
  624         if (IsExpandable(naked))                /* example: @(blah_$(blue)) */
  625         {
  626             returnval = ExpandPrivateRval(ctx, NULL, "this", rval.item, rval.type);
  627         }
  628         else
  629         {
  630             VarRef *ref = VarRefParseFromScope(naked, scope);
  631             DataType value_type;
  632             const void *value = EvalContextVariableGet(ctx, ref, &value_type);
  633             VarRefDestroy(ref);
  634 
  635             if (DataTypeToRvalType(value_type) == RVAL_TYPE_LIST)
  636             {
  637                 returnval.item = ExpandList(ctx, ns, scope, value, true);
  638                 returnval.type = RVAL_TYPE_LIST;
  639             }
  640             else
  641             {
  642                 returnval = ExpandPrivateRval(ctx, NULL, "this", rval.item, rval.type);
  643             }
  644         }
  645     }
  646     else if (forcelist) /* We are replacing scalar @(name) with list */
  647     {
  648         returnval = ExpandPrivateRval(ctx, ns, scope, rval.item, rval.type);
  649     }
  650     else if (FnCallIsBuiltIn(rval))
  651     {
  652         returnval = RvalCopy(rval);
  653     }
  654     else
  655     {
  656         returnval = ExpandPrivateRval(ctx, NULL, "this", rval.item, rval.type);
  657     }
  658 
  659     switch (returnval.type)
  660     {
  661     case RVAL_TYPE_SCALAR:
  662     case RVAL_TYPE_CONTAINER:
  663         break;
  664 
  665     case RVAL_TYPE_LIST:
  666         for (Rlist *rp = RvalRlistValue(returnval); rp; rp = rp->next)
  667         {
  668             switch (rp->val.type)
  669             {
  670             case RVAL_TYPE_FNCALL:
  671             {
  672                 FnCall *fp = RlistFnCallValue(rp);
  673                 rp->val = FnCallEvaluate(ctx, policy, fp, pp).rval;
  674                 FnCallDestroy(fp);
  675                 break;
  676             }
  677             case RVAL_TYPE_SCALAR:
  678                 if (EvalContextStackCurrentPromise(ctx) &&
  679                     IsCf3VarString(RlistScalarValue(rp)))
  680                 {
  681                     void *prior = rp->val.item;
  682                     rp->val = ExpandPrivateRval(ctx, NULL, "this",
  683                                                 prior, RVAL_TYPE_SCALAR);
  684                     free(prior);
  685                 }
  686                 /* else: returnval unchanged. */
  687                 break;
  688             default:
  689                 assert(!"Bad type for entry in Rlist");
  690             }
  691         }
  692         break;
  693 
  694     case RVAL_TYPE_FNCALL:
  695         if (FnCallIsBuiltIn(returnval))
  696         {
  697             FnCall *fp = RvalFnCallValue(returnval);
  698             returnval = FnCallEvaluate(ctx, policy, fp, pp).rval;
  699             FnCallDestroy(fp);
  700         }
  701         break;
  702 
  703     default:
  704         assert(returnval.item == NULL); /* else we're leaking it */
  705         returnval.item = NULL;
  706         returnval.type = RVAL_TYPE_NOPROMISEE;
  707         break;
  708     }
  709 
  710     return returnval;
  711 }
  712 
  713 /*********************************************************************/
  714 
  715 void BundleResolvePromiseType(EvalContext *ctx, const Bundle *bundle, const char *type, PromiseActuator *actuator)
  716 {
  717     for (size_t j = 0; j < SeqLength(bundle->promise_types); j++)
  718     {
  719         PromiseType *pt = SeqAt(bundle->promise_types, j);
  720 
  721         if (strcmp(pt->name, type) == 0)
  722         {
  723             EvalContextStackPushPromiseTypeFrame(ctx, pt);
  724             for (size_t i = 0; i < SeqLength(pt->promises); i++)
  725             {
  726                 Promise *pp = SeqAt(pt->promises, i);
  727                 ExpandPromise(ctx, pp, actuator, NULL);
  728             }
  729             EvalContextStackPopFrame(ctx);
  730         }
  731     }
  732 }
  733 
  734 static int PointerCmp(const void *a, const void *b, ARG_UNUSED void *user_data)
  735 {
  736     if (a < b)
  737     {
  738         return -1;
  739     }
  740     else if (a == b)
  741     {
  742         return 0;
  743     }
  744     else
  745     {
  746         return 1;
  747     }
  748 }
  749 
  750 static void RemoveRemotelyInjectedVars(const EvalContext *ctx, const Bundle *bundle)
  751 {
  752     const Seq *remote_var_promises = EvalContextGetRemoteVarPromises(ctx, bundle->name);
  753     if ((remote_var_promises == NULL) || SeqLength(remote_var_promises) == 0)
  754     {
  755         /* nothing to do here */
  756         return;
  757     }
  758 
  759     size_t promises_length = SeqLength(remote_var_promises);
  760     Seq *remove_vars = SeqNew(promises_length, NULL);
  761 
  762     /* remove variables that have been attempted to be inserted into this
  763      * bundle */
  764     /* TODO: this is expensive and should be removed! */
  765     for (size_t i = 0; i < promises_length; i++)
  766     {
  767         const Promise *pp = (Promise *) SeqAt(remote_var_promises, i);
  768 
  769         VariableTableIterator *iter = EvalContextVariableTableIteratorNew(ctx, NULL, bundle->name, NULL);
  770         const Variable *var = VariableTableIteratorNext(iter);
  771         while (var != NULL)
  772         {
  773             /* variables are stored together with their original promises (org_pp) */
  774             if (var->promise && var->promise->org_pp == pp)
  775             {
  776                 Log(LOG_LEVEL_ERR, "Ignoring remotely-injected variable '%s'",
  777                     var->ref->lval);
  778                 /* avoid modifications of the variable table being iterated
  779                  * over and avoid trying to remove the same variable twice */
  780                 SeqAppendOnce(remove_vars, (void *) var, PointerCmp);
  781             }
  782             var = VariableTableIteratorNext(iter);
  783         }
  784         VariableTableIteratorDestroy(iter);
  785     }
  786 
  787     /* iteration over the variable table done, time to remove the variables */
  788     size_t remove_vars_length = SeqLength(remove_vars);
  789     for (size_t i = 0; i < remove_vars_length; i++)
  790     {
  791         Variable *var = (Variable *) SeqAt(remove_vars, i);
  792         if (var->ref != NULL)
  793         {
  794             EvalContextVariableRemove(ctx, var->ref);
  795         }
  796     }
  797     SeqDestroy(remove_vars);
  798 }
  799 
  800 void BundleResolve(EvalContext *ctx, const Bundle *bundle)
  801 {
  802     Log(LOG_LEVEL_DEBUG,
  803         "Resolving classes and variables in 'bundle %s %s'",
  804         bundle->type, bundle->name);
  805 
  806     /* first check if some variables were injected remotely into this bundle and
  807      * remove them (CFE-1915) */
  808     RemoveRemotelyInjectedVars(ctx, bundle);
  809 
  810     /* PRE-EVAL: evaluate classes of common bundles. */
  811     if (strcmp(bundle->type, "common") == 0)
  812     {
  813         /* Necessary to parse vars *before* classes for cases like this:
  814          * 00_basics/04_bundles/dynamic_bundlesequence/dynamic_inputs_based_on_class_set_using_variable_file_control_extends_inputs.cf.sub
  815          *   --  see bundle "classify". */
  816         BundleResolvePromiseType(ctx, bundle, "vars", VerifyVarPromise);
  817 
  818         BundleResolvePromiseType(ctx, bundle, "classes", VerifyClassPromise);
  819     }
  820 
  821     /* Necessary to also parse vars *after* classes,
  822      * because "inputs" might be affected in cases like:
  823      * 00_basics/04_bundles/dynamic_bundlesequence/dynamic_inputs_based_on_list_variable_dependent_on_class.cf */
  824     BundleResolvePromiseType(ctx, bundle, "vars", VerifyVarPromise);
  825 }
  826 
  827 /**
  828  * Evaluate the relevant control body, and set the
  829  * relevant fields in #ctx and #config.
  830  */
  831 static void ResolveControlBody(EvalContext *ctx, GenericAgentConfig *config,
  832                                const Body *control_body)
  833 {
  834     const char *filename = control_body->source_path;
  835 
  836     assert(CFG_CONTROLBODY[COMMON_CONTROL_MAX].lval == NULL);
  837 
  838     const ConstraintSyntax *body_syntax = NULL;
  839     for (int i = 0; CONTROL_BODIES[i].constraints != NULL; i++)
  840     {
  841         body_syntax = CONTROL_BODIES[i].constraints;
  842 
  843         if (strcmp(control_body->type, CONTROL_BODIES[i].body_type) == 0)
  844         {
  845             break;
  846         }
  847     }
  848     if (body_syntax == NULL)
  849     {
  850         FatalError(ctx, "Unknown control body: %s", control_body->type);
  851     }
  852 
  853     char *scope;
  854     assert(strcmp(control_body->name, "control") == 0);
  855     xasprintf(&scope, "control_%s", control_body->type);
  856 
  857     Log(LOG_LEVEL_DEBUG, "Initiate control variable convergence for scope '%s'", scope);
  858 
  859     EvalContextStackPushBodyFrame(ctx, NULL, control_body, NULL);
  860 
  861     for (size_t i = 0; i < SeqLength(control_body->conlist); i++)
  862     {
  863         const char *lval;
  864         Rval evaluated_rval;
  865         size_t lineno;
  866 
  867         /* Use nested scope to constrain cp. */
  868         {
  869             Constraint *cp = SeqAt(control_body->conlist, i);
  870             lval   = cp->lval;
  871             lineno = cp->offset.line;
  872 
  873             if (!IsDefinedClass(ctx, cp->classes))
  874             {
  875                 continue;
  876             }
  877 
  878             if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_BUNDLESEQUENCE].lval) == 0)
  879             {
  880                 evaluated_rval = ExpandPrivateRval(ctx, NULL, scope,
  881                                                    cp->rval.item, cp->rval.type);
  882             }
  883             else
  884             {
  885                 evaluated_rval = EvaluateFinalRval(ctx, control_body->parent_policy,
  886                                                    NULL, scope, cp->rval,
  887                                                    true, NULL);
  888             }
  889 
  890         } /* Close scope: assert we only use evaluated_rval, not cp->rval. */
  891 
  892         VarRef *ref = VarRefParseFromScope(lval, scope);
  893         EvalContextVariableRemove(ctx, ref);
  894 
  895         DataType rval_proper_datatype =
  896             ConstraintSyntaxGetDataType(body_syntax, lval);
  897         if (evaluated_rval.type != DataTypeToRvalType(rval_proper_datatype))
  898         {
  899             Log(LOG_LEVEL_ERR,
  900                 "Attribute '%s' in %s:%zu is of wrong type, skipping",
  901                 lval, filename, lineno);
  902             VarRefDestroy(ref);
  903             RvalDestroy(evaluated_rval);
  904             continue;
  905         }
  906 
  907         bool success = EvalContextVariablePut(
  908             ctx, ref, evaluated_rval.item, rval_proper_datatype,
  909             "source=promise");
  910         if (!success)
  911         {
  912             Log(LOG_LEVEL_ERR,
  913                 "Attribute '%s' in %s:%zu can't be added, skipping",
  914                 lval, filename, lineno);
  915             VarRefDestroy(ref);
  916             RvalDestroy(evaluated_rval);
  917             continue;
  918         }
  919 
  920         VarRefDestroy(ref);
  921 
  922         if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_OUTPUT_PREFIX].lval) == 0)
  923         {
  924             strlcpy(VPREFIX, RvalScalarValue(evaluated_rval),
  925                     sizeof(VPREFIX));
  926         }
  927 
  928         if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_DOMAIN].lval) == 0)
  929         {
  930             strlcpy(VDOMAIN, RvalScalarValue(evaluated_rval),
  931                     sizeof(VDOMAIN));
  932             Log(LOG_LEVEL_VERBOSE, "SET domain = %s", VDOMAIN);
  933 
  934             EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_SYS, "domain");
  935             EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_SYS, "fqhost");
  936             snprintf(VFQNAME, CF_MAXVARSIZE, "%s.%s", VUQNAME, VDOMAIN);
  937             EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "fqhost",
  938                                           VFQNAME, CF_DATA_TYPE_STRING,
  939                                           "inventory,source=agent,attribute_name=Host name");
  940             EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "domain",
  941                                           VDOMAIN, CF_DATA_TYPE_STRING,
  942                                           "source=agent");
  943             EvalContextClassPutHard(ctx, VDOMAIN, "source=agent");
  944         }
  945 
  946         if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_IGNORE_MISSING_INPUTS].lval) == 0)
  947         {
  948             Log(LOG_LEVEL_VERBOSE, "SET ignore_missing_inputs %s",
  949                 RvalScalarValue(evaluated_rval));
  950             config->ignore_missing_inputs = BooleanFromString(
  951                 RvalScalarValue(evaluated_rval));
  952         }
  953 
  954         if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_IGNORE_MISSING_BUNDLES].lval) == 0)
  955         {
  956             Log(LOG_LEVEL_VERBOSE, "SET ignore_missing_bundles %s",
  957                 RvalScalarValue(evaluated_rval));
  958             config->ignore_missing_bundles = BooleanFromString(
  959                 RvalScalarValue(evaluated_rval));
  960         }
  961 
  962         if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_CACHE_SYSTEM_FUNCTIONS].lval) == 0)
  963         {
  964             Log(LOG_LEVEL_VERBOSE, "SET cache_system_functions %s",
  965                 RvalScalarValue(evaluated_rval));
  966             bool cache_system_functions = BooleanFromString(
  967                 RvalScalarValue(evaluated_rval));
  968             EvalContextSetEvalOption(ctx, EVAL_OPTION_CACHE_SYSTEM_FUNCTIONS,
  969                                      cache_system_functions);
  970         }
  971 
  972         if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_PROTOCOL_VERSION].lval) == 0)
  973         {
  974             config->protocol_version = ProtocolVersionParse(
  975                 RvalScalarValue(evaluated_rval));
  976             Log(LOG_LEVEL_VERBOSE, "SET common protocol_version: %s",
  977                 ProtocolVersionString(config->protocol_version));
  978         }
  979 
  980         /* Those are package_inventory and package_module common control body options */
  981         if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_PACKAGE_INVENTORY].lval) == 0)
  982         {
  983             AddDefaultInventoryToContext(ctx, RvalRlistValue(evaluated_rval));
  984             Log(LOG_LEVEL_VERBOSE, "SET common package_inventory list");
  985         }
  986         if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_PACKAGE_MODULE].lval) == 0)
  987         {
  988             AddDefaultPackageModuleToContext(ctx, RvalScalarValue(evaluated_rval));
  989             Log(LOG_LEVEL_VERBOSE, "SET common package_module: %s",
  990                 RvalScalarValue(evaluated_rval));
  991         }
  992 
  993         if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_GOALPATTERNS].lval) == 0)
  994         {
  995             /* Ignored */
  996         }
  997 
  998         RvalDestroy(evaluated_rval);
  999     }
 1000 
 1001     EvalContextStackPopFrame(ctx);
 1002     free(scope);
 1003 }
 1004 
 1005 static void ResolvePackageManagerBody(EvalContext *ctx, const Body *pm_body)
 1006 {
 1007     PackageModuleBody *new_manager = xcalloc(1, sizeof(PackageModuleBody));
 1008     new_manager->name = SafeStringDuplicate(pm_body->name);
 1009 
 1010     for (size_t i = 0; i < SeqLength(pm_body->conlist); i++)
 1011     {
 1012         Constraint *cp = SeqAt(pm_body->conlist, i);
 1013 
 1014         Rval returnval = {0};
 1015 
 1016         if (IsDefinedClass(ctx, cp->classes))
 1017         {
 1018             returnval = ExpandPrivateRval(ctx, NULL, "body",
 1019                                           cp->rval.item, cp->rval.type);
 1020         }
 1021 
 1022         if (returnval.item == NULL || returnval.type == RVAL_TYPE_NOPROMISEE)
 1023         {
 1024             Log(LOG_LEVEL_VERBOSE, "have invalid constraint while resolving"
 1025                     "package promise body: %s", cp->lval);
 1026 
 1027             RvalDestroy(returnval);
 1028             continue;
 1029         }
 1030 
 1031         if (strcmp(cp->lval, "query_installed_ifelapsed") == 0)
 1032         {
 1033             new_manager->installed_ifelapsed =
 1034                     (int)IntFromString(RvalScalarValue(returnval));
 1035         }
 1036         else if (strcmp(cp->lval, "query_updates_ifelapsed") == 0)
 1037         {
 1038             new_manager->updates_ifelapsed =
 1039                     (int)IntFromString(RvalScalarValue(returnval));
 1040         }
 1041         else if (strcmp(cp->lval, "default_options") == 0)
 1042         {
 1043             new_manager->options = RlistCopy(RvalRlistValue(returnval));
 1044         }
 1045         else if (strcmp(cp->lval, "interpreter") == 0)
 1046         {
 1047             assert(new_manager->interpreter == NULL);
 1048             new_manager->interpreter = SafeStringDuplicate(RvalScalarValue(returnval));
 1049         }
 1050         else if (strcmp(cp->lval, "module_path") == 0)
 1051         {
 1052             assert(new_manager->module_path == NULL);
 1053             new_manager->module_path = SafeStringDuplicate(RvalScalarValue(returnval));
 1054         }
 1055         else
 1056         {
 1057             /* This should be handled by the parser. */
 1058             assert(0);
 1059         }
 1060         RvalDestroy(returnval);
 1061     }
 1062     AddPackageModuleToContext(ctx, new_manager);
 1063 }
 1064 
 1065 void PolicyResolve(EvalContext *ctx, const Policy *policy,
 1066                    GenericAgentConfig *config)
 1067 {
 1068     /* PRE-EVAL: common bundles: classes,vars. */
 1069     for (size_t i = 0; i < SeqLength(policy->bundles); i++)
 1070     {
 1071         Bundle *bundle = SeqAt(policy->bundles, i);
 1072         if (strcmp("common", bundle->type) == 0)
 1073         {
 1074             EvalContextStackPushBundleFrame(ctx, bundle, NULL, false);
 1075             BundleResolve(ctx, bundle);            /* PRE-EVAL classes,vars */
 1076             EvalContextStackPopFrame(ctx);
 1077         }
 1078     }
 1079 
 1080 /*
 1081  * HACK: yet another pre-eval pass here, WHY? TODO remove, but test fails:
 1082  *       00_basics/03_bodies/dynamic_inputs_findfiles.cf
 1083  */
 1084 #if 1
 1085 
 1086     /* PRE-EVAL: non-common bundles: only vars. */
 1087     for (size_t i = 0; i < SeqLength(policy->bundles); i++)
 1088     {
 1089         Bundle *bundle = SeqAt(policy->bundles, i);
 1090         if (strcmp("common", bundle->type) != 0)
 1091         {
 1092             EvalContextStackPushBundleFrame(ctx, bundle, NULL, false);
 1093             BundleResolve(ctx, bundle);                    /* PRE-EVAL vars */
 1094             EvalContextStackPopFrame(ctx);
 1095         }
 1096     }
 1097 
 1098 #endif
 1099 
 1100     for (size_t i = 0; i < SeqLength(policy->bodies); i++)
 1101     {
 1102         Body *bdp = SeqAt(policy->bodies, i);
 1103 
 1104         if (strcmp(bdp->name, "control") == 0)
 1105         {
 1106             ResolveControlBody(ctx, config, bdp);
 1107         }
 1108         /* Collect all package managers data from policy as we don't know yet
 1109          * which ones we will use. */
 1110         else if (strcmp(bdp->type, "package_module") == 0)
 1111         {
 1112             ResolvePackageManagerBody(ctx, bdp);
 1113         }
 1114     }
 1115 }
 1116 
 1117 bool IsExpandable(const char *str)
 1118 {
 1119     char left = 'x', right = 'x';
 1120     int dollar = false;
 1121     int bracks = 0, vars = 0;
 1122 
 1123     for (const char *sp = str; *sp != '\0'; sp++)   /* check for varitems */
 1124     {
 1125         switch (*sp)
 1126         {
 1127         case '$':
 1128             if (*(sp + 1) == '{' || *(sp + 1) == '(')
 1129             {
 1130                 dollar = true;
 1131             }
 1132             break;
 1133         case '(':
 1134         case '{':
 1135             if (dollar)
 1136             {
 1137                 left = *sp;
 1138                 bracks++;
 1139             }
 1140             break;
 1141         case ')':
 1142         case '}':
 1143             if (dollar)
 1144             {
 1145                 bracks--;
 1146                 right = *sp;
 1147             }
 1148             break;
 1149         }
 1150 
 1151         if (left == '(' && right == ')' && dollar && (bracks == 0))
 1152         {
 1153             vars++;
 1154             dollar = false;
 1155         }
 1156 
 1157         if (left == '{' && right == '}' && dollar && (bracks == 0))
 1158         {
 1159             vars++;
 1160             dollar = false;
 1161         }
 1162     }
 1163 
 1164     if (bracks != 0)
 1165     {
 1166         Log(LOG_LEVEL_DEBUG, "If this is an expandable variable string then it contained syntax errors");
 1167         return false;
 1168     }
 1169 
 1170     if (vars > 0)
 1171     {
 1172         Log(LOG_LEVEL_DEBUG,
 1173             "Expanding variable '%s': found %d variables", str, vars);
 1174     }
 1175     return (vars > 0);
 1176 }
 1177 
 1178 /*********************************************************************/
 1179 
 1180 static inline char opposite(char c)
 1181 {
 1182     switch (c)
 1183     {
 1184     case '(':  return ')';
 1185     case '{':  return '}';
 1186     default :  ProgrammingError("Was expecting '(' or '{' but got: '%c'", c);
 1187     }
 1188     return 0;
 1189 }
 1190 
 1191 /**
 1192  * Check if #str contains one and only one variable expansion of #vtype kind
 1193  * (it's usually either '$' or '@'). It can contain nested expansions which
 1194  * are not checked properly. Examples:
 1195  *     true:  "$(whatever)", "${whatever}", "$(blah$(blue))"
 1196  *     false: "$(blah)blue", "blah$(blue)", "$(blah)$(blue)", "$(blah}"
 1197  */
 1198 bool IsNakedVar(const char *str, char vtype)
 1199 {
 1200     size_t len = strlen(str);
 1201     char last  = len > 0 ? str[len-1] : '\0';
 1202 
 1203     if (len < 3
 1204         || str[0] != vtype
 1205         || (str[1] != '(' && str[1] != '{')
 1206         || last != opposite(str[1]))
 1207     {
 1208         return false;
 1209     }
 1210 
 1211     /* TODO check if nesting happens correctly? Is it needed? */
 1212     size_t count = 0;
 1213     for (const char *sp = str; *sp != '\0'; sp++)
 1214     {
 1215         switch (*sp)
 1216         {
 1217         case '(':
 1218         case '{':
 1219             count++;
 1220             break;
 1221         case ')':
 1222         case '}':
 1223             count--;
 1224 
 1225             /* Make sure the end of the variable is the last character. */
 1226             if (count == 0 && sp[1] != '\0')
 1227             {
 1228                 return false;
 1229             }
 1230 
 1231             break;
 1232         }
 1233     }
 1234 
 1235     if (count != 0)
 1236     {
 1237         return false;
 1238     }
 1239 
 1240     return true;
 1241 }
 1242 
 1243 /*********************************************************************/
 1244 
 1245 /**
 1246  * Copy @(listname) -> listname.
 1247  *
 1248  * This function performs no validations, it is necessary to call the
 1249  * validation functions before calling this function.
 1250  *
 1251  * @NOTE make sure sizeof(dst) >= sizeof(s)
 1252  */
 1253 void GetNaked(char *dst, const char *s)
 1254 {
 1255     size_t s_len = strlen(s);
 1256 
 1257     if (s_len < 4  ||  s_len + 3 >= CF_MAXVARSIZE)
 1258     {
 1259         Log(LOG_LEVEL_ERR,
 1260             "@(variable) expected, but got malformed: %s", s);
 1261         strlcpy(dst, s, CF_MAXVARSIZE);
 1262         return;
 1263     }
 1264 
 1265     memcpy(dst, &s[2], s_len - 3);
 1266     dst[s_len - 3] = '\0';
 1267 }
 1268 
 1269 /*********************************************************************/
 1270 
 1271 /**
 1272  * Checks if a variable is an @-list and returns true or false.
 1273  */
 1274 bool IsVarList(const char *var)
 1275 {
 1276     if ('@' != var[0])
 1277     {
 1278         return false;
 1279     }
 1280     /*
 1281      * Minimum size for a list is 4:
 1282      * '@' + '(' + name + ')'
 1283      */
 1284     if (strlen(var) < 4)
 1285     {
 1286         return false;
 1287     }
 1288     return true;
 1289 }
 1290 
 1291 PromiseResult CommonEvalPromise(EvalContext *ctx, const Promise *pp,
 1292                                 ARG_UNUSED void *param)
 1293 {
 1294     assert(param == NULL);
 1295 
 1296     PromiseRecheckAllConstraints(ctx, pp);
 1297 
 1298     return PROMISE_RESULT_NOOP;
 1299 }