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 <eval_context.h>
26 
27 #include <files_names.h>
28 #include <logic_expressions.h>
29 #include <syntax.h>
30 #include <item_lib.h>
31 #include <ornaments.h>
32 #include <expand.h>                                    /* ExpandPrivateRval */
33 #include <matching.h>
34 #include <string_lib.h>
35 #include <misc_lib.h>
36 #include <file_lib.h>
37 #include <assoc.h>
38 #include <scope.h>
39 #include <vars.h>
40 #include <syslog_client.h>
41 #include <audit.h>
42 #include <rlist.h>
43 #include <buffer.h>
44 #include <promises.h>
45 #include <fncall.h>
46 #include <ring_buffer.h>
47 #include <logging_priv.h>
48 #include <known_dirs.h>
49 #include <printsize.h>
50 #include <regex.h>
51 #include <map.h>
52 #include <conversion.h>                               /* DataTypeIsIterable */
53 #include <cleanup.h>
54 
55 /* If we need to put a scoped variable into a special scope, use the string
56  * below to replace the original scope separator.
57  * (e.g. "config.var" -> "this.config___var" ) */
58 #define NESTED_SCOPE_SEP "___"
59 
60 static const char *STACK_FRAME_TYPE_STR[STACK_FRAME_TYPE_MAX] = {
61     "BUNDLE",
62     "BODY",
63     "PROMISE_TYPE",
64     "PROMISE",
65     "PROMISE_ITERATION"
66 };
67 
68 
69 /**
70    Define FuncCacheMap.
71    Key:   an Rlist (which is linked list of Rvals)
72           listing all the argument of the function
73    Value: an Rval, the result of the function
74  */
75 
RvalDestroy2(void * p)76 static void RvalDestroy2(void *p)
77 {
78     Rval *rv = p;
79     RvalDestroy(*rv);
80     free(rv);
81 }
82 
TYPED_MAP_DECLARE(FuncCache,Rlist *,Rval *)83 TYPED_MAP_DECLARE(FuncCache, Rlist *, Rval *)
84 
85 TYPED_MAP_DEFINE(FuncCache, Rlist *, Rval *,
86                  RlistHash_untyped,
87                  RlistEqual_untyped,
88                  RlistDestroy_untyped,
89                  RvalDestroy2)
90 
91 /**
92    Define RemoteVarsPromisesMap.
93    Key:   bundle name (char *)
94    Value: a sequence of promises (const *Promise), only the container
95           (sequence) should be deallocated)
96  */
97 
98 static void SeqDestroy_untyped(void *p)
99 {
100     Seq *s = p;
101     SeqDestroy(s);
102 }
103 
104 TYPED_MAP_DECLARE(RemoteVarPromises, char *, Seq *)
105 
106 TYPED_MAP_DEFINE(RemoteVarPromises, char *, Seq *,
107                  StringHash_untyped,
108                  StringEqual_untyped,
109                  free,
110                  SeqDestroy_untyped)
111 
112 
113 static pcre *context_expression_whitespace_rx = NULL;
114 
115 #include <policy.h>
116 
117 static bool BundleAborted(const EvalContext *ctx);
118 static void SetBundleAborted(EvalContext *ctx);
119 static void SetEvalAborted(EvalContext *ctx);
120 
121 static bool EvalContextStackFrameContainsSoft(const EvalContext *ctx, const char *context);
122 static bool EvalContextHeapContainsSoft(const EvalContext *ctx, const char *ns, const char *name);
123 static bool EvalContextHeapContainsHard(const EvalContext *ctx, const char *name);
124 static bool EvalContextClassPut(EvalContext *ctx, const char *ns, const char *name,
125                                 bool is_soft, ContextScope scope,
126                                 const char *tags, const char *comment);
127 static const char *EvalContextCurrentNamespace(const EvalContext *ctx);
128 static ClassRef IDRefQualify(const EvalContext *ctx, const char *id);
129 
130 /**
131  * Every agent has only one EvalContext from process start to finish.
132  */
133 struct EvalContext_
134 {
135     /* TODO: a pointer to read-only version of config is often needed. */
136     /* const GenericAgentConfig *config; */
137 
138     int eval_options;
139     bool bundle_aborted;
140     bool eval_aborted;
141     bool checksum_updates_default;
142     Item *ip_addresses;
143     bool ignore_locks;
144 
145     int pass;
146     Rlist *args;
147 
148     Item *heap_abort;
149     Item *heap_abort_current_bundle;
150 
151     Seq *stack;
152 
153     ClassTable *global_classes;
154     VariableTable *global_variables;
155 
156     VariableTable *match_variables;
157 
158     StringSet *promise_lock_cache;
159     StringSet *dependency_handles;
160     FuncCacheMap *function_cache;
161 
162     uid_t uid;
163     uid_t gid;
164     pid_t pid;
165     pid_t ppid;
166 
167     // Full path to directory that the binary was launched from.
168     char *launch_directory;
169 
170     char *entry_point;
171 
172     /* new package promise evaluation context */
173     PackagePromiseContext *package_promise_context;
174 
175     /* select_end_match_eof context*/
176     bool select_end_match_eof;
177 
178     /* List if all classes set during policy evaluation */
179     StringSet *all_classes;
180 
181     /* Negated classes (persistent classes that should not be defined). */
182     StringSet *negated_classes;
183 
184     /* These following two fields are needed for remote variable injection
185      * detection (CFE-1915) */
186     /* Names of all bundles */
187     StringSet *bundle_names;
188 
189     /* Promises possibly remotely-injecting variables */
190     /* ONLY INITIALIZED WHEN NON-EMPTY, OTHERWISE NULL */
191     RemoteVarPromisesMap *remote_var_promises;
192 
193     bool dump_reports;
194 };
195 
EvalContextGetSelectEndMatchEof(const EvalContext * ctx)196 bool EvalContextGetSelectEndMatchEof(const EvalContext *ctx)
197 {
198     return ctx->select_end_match_eof;
199 }
200 
EvalContextSetSelectEndMatchEof(EvalContext * ctx,bool value)201 void EvalContextSetSelectEndMatchEof(EvalContext *ctx, bool value)
202 {
203     ctx->select_end_match_eof = value;
204 }
205 
206 
AddDefaultPackageModuleToContext(const EvalContext * ctx,char * name)207 void AddDefaultPackageModuleToContext(const EvalContext *ctx, char *name)
208 {
209     assert(ctx);
210     assert(ctx->package_promise_context);
211 
212     free(ctx->package_promise_context->control_package_module);
213     ctx->package_promise_context->control_package_module =
214             SafeStringDuplicate(name);
215 }
216 
AddDefaultInventoryToContext(const EvalContext * ctx,Rlist * inventory)217 void AddDefaultInventoryToContext(const EvalContext *ctx, Rlist *inventory)
218 {
219     assert(ctx);
220     assert(ctx->package_promise_context);
221 
222     RlistDestroy(ctx->package_promise_context->control_package_inventory);
223     ctx->package_promise_context->control_package_inventory =
224             RlistCopy(inventory);
225 }
226 
227 static
PackageManagerSeqCompare(const void * a,const void * b,ARG_UNUSED void * data)228 int PackageManagerSeqCompare(const void *a, const void *b, ARG_UNUSED void *data)
229 {
230     return StringSafeCompare((char*)a, ((PackageModuleBody*)b)->name);
231 }
232 
AddPackageModuleToContext(const EvalContext * ctx,PackageModuleBody * pm)233 void AddPackageModuleToContext(const EvalContext *ctx, PackageModuleBody *pm)
234 {
235     assert(ctx != NULL);
236     assert(pm != NULL);
237 
238     /* First check if the body is there added from previous pre-evaluation
239      * iteration. If it is there update it as we can have new expanded variables. */
240     Seq *const bodies = ctx->package_promise_context->package_modules_bodies;
241     ssize_t index = SeqIndexOf(bodies, pm->name, PackageManagerSeqCompare);
242     if (index != -1)
243     {
244         SeqRemove(bodies, index);
245     }
246     SeqAppend(bodies, pm);
247 }
248 
GetPackageModuleFromContext(const EvalContext * ctx,const char * name)249 PackageModuleBody *GetPackageModuleFromContext(const EvalContext *ctx,
250         const char *name)
251 {
252     if (name == NULL)
253     {
254         return NULL;
255     }
256 
257     for (size_t i = 0;
258          i < SeqLength(ctx->package_promise_context->package_modules_bodies);
259          i++)
260     {
261         PackageModuleBody *pm =
262                 SeqAt(ctx->package_promise_context->package_modules_bodies, i);
263         if (strcmp(name, pm->name) == 0)
264         {
265             return pm;
266         }
267     }
268     return NULL;
269 }
270 
GetDefaultPackageModuleFromContext(const EvalContext * ctx)271 PackageModuleBody *GetDefaultPackageModuleFromContext(const EvalContext *ctx)
272 {
273     char *def_pm_name = ctx->package_promise_context->control_package_module;
274     return GetPackageModuleFromContext(ctx, def_pm_name);
275 }
276 
GetDefaultInventoryFromContext(const EvalContext * ctx)277 Rlist *GetDefaultInventoryFromContext(const EvalContext *ctx)
278 {
279     return ctx->package_promise_context->control_package_inventory;
280 }
281 
GetPackagePromiseContext(const EvalContext * ctx)282 PackagePromiseContext *GetPackagePromiseContext(const EvalContext *ctx)
283 {
284     return ctx->package_promise_context;
285 }
286 
287 
LastStackFrame(const EvalContext * ctx,size_t offset)288 static StackFrame *LastStackFrame(const EvalContext *ctx, size_t offset)
289 {
290     if (SeqLength(ctx->stack) <= offset)
291     {
292         return NULL;
293     }
294     return SeqAt(ctx->stack, SeqLength(ctx->stack) - 1 - offset);
295 }
296 
LastStackFrameByType(const EvalContext * ctx,StackFrameType type)297 static StackFrame *LastStackFrameByType(const EvalContext *ctx, StackFrameType type)
298 {
299     for (size_t i = 0; i < SeqLength(ctx->stack); i++)
300     {
301         StackFrame *frame = LastStackFrame(ctx, i);
302         if (frame->type == type)
303         {
304             return frame;
305         }
306     }
307 
308     return NULL;
309 }
310 
AdjustLogLevel(LogLevel base,LogLevel adjust)311 static LogLevel AdjustLogLevel(LogLevel base, LogLevel adjust)
312 {
313     if (adjust == -1)
314     {
315         return base;
316     }
317     else
318     {
319         return MAX(base, adjust);
320     }
321 }
322 
StringToLogLevel(const char * value)323 static LogLevel StringToLogLevel(const char *value)
324 {
325     if (value)
326     {
327         if (!strcmp(value, "verbose"))
328         {
329             return LOG_LEVEL_VERBOSE;
330         }
331         if (!strcmp(value, "inform"))
332         {
333             return LOG_LEVEL_INFO;
334         }
335         if (!strcmp(value, "error"))
336         {
337             return LOG_LEVEL_NOTICE; /* Error level includes warnings and notices */
338         }
339     }
340     return -1;
341 }
342 
GetLevelForPromise(const Promise * pp,const char * attr_name)343 static LogLevel GetLevelForPromise(const Promise *pp, const char *attr_name)
344 {
345     return StringToLogLevel(PromiseGetConstraintAsRval(pp, attr_name, RVAL_TYPE_SCALAR));
346 }
347 
CalculateLogLevel(const Promise * pp)348 static LogLevel CalculateLogLevel(const Promise *pp)
349 {
350     LogLevel global_log_level = LogGetGlobalLevel();
351     LogLevel system_log_level = LogGetGlobalSystemLogLevel();
352 
353     LogLevel log_level = (system_log_level != LOG_LEVEL_NOTHING ?
354                           system_log_level : global_log_level);
355 
356     if (pp)
357     {
358         log_level = AdjustLogLevel(log_level, GetLevelForPromise(pp, "log_level"));
359     }
360 
361     /* Disable system log for dry-runs */
362     if (DONTDO)
363     {
364         log_level = LOG_LEVEL_NOTHING;
365     }
366 
367     return log_level;
368 }
369 
CalculateReportLevel(const Promise * pp)370 static LogLevel CalculateReportLevel(const Promise *pp)
371 {
372     LogLevel report_level = LogGetGlobalLevel();
373 
374     if (pp)
375     {
376         report_level = AdjustLogLevel(report_level, GetLevelForPromise(pp, "report_level"));
377     }
378 
379     return report_level;
380 }
381 
EvalContextStackToString(EvalContext * ctx)382 const char *EvalContextStackToString(EvalContext *ctx)
383 {
384     StackFrame *last_frame = LastStackFrame(ctx, 0);
385     if (last_frame)
386     {
387         return last_frame->path;
388     }
389     return "";
390 }
391 
GetAgentAbortingContext(const EvalContext * ctx)392 static const char *GetAgentAbortingContext(const EvalContext *ctx)
393 {
394     for (const Item *ip = ctx->heap_abort; ip != NULL; ip = ip->next)
395     {
396         if (IsDefinedClass(ctx, ip->classes))
397         {
398             const char *regex = ip->name;
399             Class *cls = EvalContextClassMatch(ctx, regex);
400 
401             if (cls)
402             {
403                 return cls->name;
404             }
405         }
406     }
407     return NULL;
408 }
409 
EvalContextStackFrameAddSoft(EvalContext * ctx,const char * context,const char * tags)410 static void EvalContextStackFrameAddSoft(EvalContext *ctx, const char *context, const char *tags)
411 {
412     assert(SeqLength(ctx->stack) > 0);
413 
414     StackFrameBundle frame;
415     {
416         StackFrame *last_frame = LastStackFrameByType(ctx, STACK_FRAME_TYPE_BUNDLE);
417         if (!last_frame)
418         {
419             ProgrammingError("Attempted to add a soft class on the stack, but stack had no bundle frame");
420         }
421         frame = last_frame->data.bundle;
422     }
423 
424     char copy[CF_BUFSIZE];
425     if (strcmp(frame.owner->ns, "default") != 0)
426     {
427          snprintf(copy, CF_MAXVARSIZE, "%s:%s", frame.owner->ns, context);
428     }
429     else
430     {
431          strlcpy(copy, context, CF_MAXVARSIZE);
432     }
433 
434     if (Chop(copy, CF_EXPANDSIZE) == -1)
435     {
436         Log(LOG_LEVEL_ERR, "Chop was called on a string that seemed to have no terminator");
437     }
438 
439     if (strlen(copy) == 0)
440     {
441         return;
442     }
443 
444     if (EvalContextHeapContainsSoft(ctx, frame.owner->ns, context))
445     {
446         Log(LOG_LEVEL_WARNING, "Private class '%s' in bundle '%s' shadows a global class - you should choose a different name to avoid conflicts",
447               copy, frame.owner->name);
448     }
449 
450     if (IsRegexItemIn(ctx, ctx->heap_abort_current_bundle, copy))
451     {
452         Log(LOG_LEVEL_ERR, "Bundle '%s' aborted on defined class '%s'", frame.owner->name, copy);
453         SetBundleAborted(ctx);
454     }
455 
456     if (IsRegexItemIn(ctx, ctx->heap_abort, copy))
457     {
458         Log(LOG_LEVEL_NOTICE, "cf-agent aborted on defined class '%s'", copy);
459         SetEvalAborted(ctx);
460     }
461 
462     if (EvalContextStackFrameContainsSoft(ctx, copy))
463     {
464         return;
465     }
466 
467     ClassTablePut(frame.classes, frame.owner->ns, context, true,
468                   CONTEXT_SCOPE_BUNDLE,
469                   NULL_OR_EMPTY(tags) ? NULL : StringSetFromString(tags, ','),
470                   NULL);
471 
472     if (!BundleAborted(ctx))
473     {
474         for (const Item *ip = ctx->heap_abort_current_bundle; ip != NULL; ip = ip->next)
475         {
476             if (IsDefinedClass(ctx, ip->name))
477             {
478                 Log(LOG_LEVEL_ERR, "Setting abort for '%s' when setting '%s'", ip->name, context);
479                 SetBundleAborted(ctx);
480                 break;
481             }
482         }
483     }
484 }
485 
EvalTokenAsClass(const char * classname,void * param)486 static ExpressionValue EvalTokenAsClass(const char *classname, void *param)
487 {
488     const EvalContext *ctx = param;
489     ClassRef ref = ClassRefParse(classname);
490     if (ClassRefIsQualified(ref))
491     {
492         if (strcmp(ref.ns, NamespaceDefault()) == 0)
493         {
494             if (EvalContextHeapContainsHard(ctx, ref.name))
495             {
496                 ClassRefDestroy(ref);
497                 return EXPRESSION_VALUE_TRUE;
498             }
499         }
500     }
501     else
502     {
503         if (EvalContextHeapContainsHard(ctx, ref.name))
504         {
505             ClassRefDestroy(ref);
506             return EXPRESSION_VALUE_TRUE;
507         }
508 
509         const char *ns = EvalContextCurrentNamespace(ctx);
510         if (ns)
511         {
512             ClassRefQualify(&ref, ns);
513         }
514         else
515         {
516             ClassRefQualify(&ref, NamespaceDefault());
517         }
518     }
519 
520     assert(ClassRefIsQualified(ref));
521     bool classy = (strcmp("any", ref.name) == 0 ||
522                    EvalContextHeapContainsSoft(ctx, ref.ns, ref.name) ||
523                    EvalContextStackFrameContainsSoft(ctx, ref.name));
524 
525     ClassRefDestroy(ref);
526     return (ExpressionValue) classy; // ExpressionValue extends bool
527 }
528 
529 /**********************************************************************/
530 
EvalVarRef(ARG_UNUSED const char * varname,ARG_UNUSED VarRefType type,ARG_UNUSED void * param)531 static char *EvalVarRef(ARG_UNUSED const char *varname, ARG_UNUSED VarRefType type, ARG_UNUSED void *param)
532 {
533 /*
534  * There should be no unexpanded variables when we evaluate any kind of
535  * logic expressions, until parsing of logic expression changes and they are
536  * not pre-expanded before evaluation.
537  */
538     return NULL;
539 }
540 
541 /**********************************************************************/
542 
ClassCharIsWhitespace(char ch)543 bool ClassCharIsWhitespace(char ch)
544 {
545     return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
546 }
547 
CheckClassExpression(const EvalContext * ctx,const char * context)548 ExpressionValue CheckClassExpression(const EvalContext *ctx, const char *context)
549 {
550     assert(context != NULL);
551     ParseResult res;
552 
553     if (!context)
554     {
555         // TODO: Remove this, seems like a hack
556         return EXPRESSION_VALUE_TRUE;
557     }
558 
559     if (context_expression_whitespace_rx == NULL)
560     {
561         context_expression_whitespace_rx = CompileRegex(CFENGINE_REGEX_WHITESPACE_IN_CONTEXTS);
562     }
563 
564     if (context_expression_whitespace_rx == NULL)
565     {
566         Log(LOG_LEVEL_ERR, "The context expression whitespace regular expression could not be compiled, aborting.");
567         return EXPRESSION_VALUE_ERROR;
568     }
569 
570     if (StringMatchFullWithPrecompiledRegex(context_expression_whitespace_rx, context))
571     {
572         Log(LOG_LEVEL_ERR, "class expressions can't be separated by whitespace without an intervening operator in expression '%s'", context);
573         return EXPRESSION_VALUE_ERROR;
574     }
575 
576     Buffer *condensed = BufferNewFrom(context, strlen(context));
577     BufferRewrite(condensed, &ClassCharIsWhitespace, true);
578     res = ParseExpression(BufferData(condensed), 0, BufferSize(condensed));
579     BufferDestroy(condensed);
580 
581     if (!res.result)
582     {
583         Log(LOG_LEVEL_ERR, "Unable to parse class expression '%s'", context);
584         return EXPRESSION_VALUE_ERROR;
585     }
586     else
587     {
588         ExpressionValue r = EvalExpression(res.result,
589                                            &EvalTokenAsClass, &EvalVarRef,
590                                            (void *)ctx); // controlled cast. None of these should modify EvalContext
591 
592         FreeExpression(res.result);
593         return r;
594     }
595 }
596 
597 /**********************************************************************/
598 
EvalTokenFromList(const char * token,void * param)599 static ExpressionValue EvalTokenFromList(const char *token, void *param)
600 {
601     StringSet *set = param;
602     return (ExpressionValue) StringSetContains(set, token); // EV extends bool
603 }
604 
605 /**********************************************************************/
606 
EvalWithTokenFromList(const char * expr,StringSet * token_set)607 static bool EvalWithTokenFromList(const char *expr, StringSet *token_set)
608 {
609     ParseResult res = ParseExpression(expr, 0, strlen(expr));
610 
611     if (!res.result)
612     {
613         Log(LOG_LEVEL_ERR, "Syntax error in expression '%s'", expr);
614         return false;           /* FIXME: return error */
615     }
616     else
617     {
618         ExpressionValue r = EvalExpression(res.result,
619                                            &EvalTokenFromList,
620                                            &EvalVarRef,
621                                            token_set);
622 
623         FreeExpression(res.result);
624 
625         /* r is EvalResult which could be ERROR */
626         return r == EXPRESSION_VALUE_TRUE;
627     }
628 }
629 
630 /**********************************************************************/
631 
632 /* Process result expression */
633 
EvalProcessResult(const char * process_result,StringSet * proc_attr)634 bool EvalProcessResult(const char *process_result, StringSet *proc_attr)
635 {
636     assert(process_result != NULL);
637     if (StringEqual(process_result, ""))
638     {
639         /* nothing to evaluate */
640         return false;
641     }
642     return EvalWithTokenFromList(process_result, proc_attr);
643 }
644 
645 /**********************************************************************/
646 
647 /* File result expressions */
648 
EvalFileResult(const char * file_result,StringSet * leaf_attr)649 bool EvalFileResult(const char *file_result, StringSet *leaf_attr)
650 {
651     return EvalWithTokenFromList(file_result, leaf_attr);
652 }
653 
654 /*****************************************************************************/
655 
656 
EvalContextHeapPersistentSave(EvalContext * ctx,const char * name,unsigned int ttl_minutes,PersistentClassPolicy policy,const char * tags)657 void EvalContextHeapPersistentSave(EvalContext *ctx, const char *name, unsigned int ttl_minutes,
658                                    PersistentClassPolicy policy, const char *tags)
659 {
660     assert(tags);
661 
662     time_t now = time(NULL);
663 
664     CF_DB *dbp;
665     if (!OpenDB(&dbp, dbid_state))
666     {
667         char *db_path = DBIdToPath(dbid_state);
668         Log(LOG_LEVEL_ERR, "While persisting class, unable to open database at '%s' (OpenDB: %s)",
669             db_path, GetErrorStr());
670         free(db_path);
671         return;
672     }
673 
674     ClassRef ref = IDRefQualify(ctx, name);
675     char *key = ClassRefToString(ref.ns, ref.name);
676     ClassRefDestroy(ref);
677 
678     size_t tags_length = strlen(tags) + 1;
679     size_t new_info_size = sizeof(PersistentClassInfo) + tags_length;
680 
681     PersistentClassInfo *new_info = xcalloc(1, new_info_size);
682 
683     new_info->expires = now + ttl_minutes * 60;
684     new_info->policy = policy;
685     strlcpy(new_info->tags, tags, tags_length);
686 
687     // first see if we have an existing record, and if we should bother to update
688     {
689         int existing_info_size = ValueSizeDB(dbp, key, strlen(key));
690         if (existing_info_size > 0)
691         {
692             PersistentClassInfo *existing_info = xcalloc(existing_info_size, 1);
693             if (ReadDB(dbp, key, existing_info, existing_info_size))
694             {
695                 if (existing_info->policy == CONTEXT_STATE_POLICY_PRESERVE &&
696                     now < existing_info->expires &&
697                     strcmp(existing_info->tags, new_info->tags) == 0)
698                 {
699                     Log(LOG_LEVEL_VERBOSE, "Persistent class '%s' is already in a preserved state --  %jd minutes to go",
700                         key, (intmax_t)((existing_info->expires - now) / 60));
701                     CloseDB(dbp);
702                     free(key);
703                     free(new_info);
704                     return;
705                 }
706             }
707             else
708             {
709                 Log(LOG_LEVEL_ERR, "While persisting class '%s', error reading existing value", key);
710                 CloseDB(dbp);
711                 free(key);
712                 free(new_info);
713                 return;
714             }
715         }
716     }
717 
718     Log(LOG_LEVEL_VERBOSE, "Updating persistent class '%s'", key);
719 
720     WriteDB(dbp, key, new_info, new_info_size);
721 
722     CloseDB(dbp);
723     free(key);
724     free(new_info);
725 }
726 
727 /*****************************************************************************/
728 
EvalContextHeapPersistentRemove(const char * context)729 void EvalContextHeapPersistentRemove(const char *context)
730 {
731     CF_DB *dbp;
732 
733     if (!OpenDB(&dbp, dbid_state))
734     {
735         return;
736     }
737 
738     DeleteDB(dbp, context);
739     Log(LOG_LEVEL_DEBUG, "Deleted persistent class '%s'", context);
740     CloseDB(dbp);
741 }
742 
743 /*****************************************************************************/
744 
EvalContextHeapPersistentLoadAll(EvalContext * ctx)745 void EvalContextHeapPersistentLoadAll(EvalContext *ctx)
746 {
747     assert(ctx != NULL);
748 
749     time_t now = time(NULL);
750 
751     Log(LOG_LEVEL_VERBOSE, "Loading persistent classes");
752 
753     CF_DB *dbp;
754     if (!OpenDB(&dbp, dbid_state))
755     {
756         return;
757     }
758 
759     CF_DBC *dbcp;
760     if (!NewDBCursor(dbp, &dbcp))
761     {
762         Log(LOG_LEVEL_INFO, "Unable to scan persistence cache");
763         return;
764     }
765 
766     const char *key;
767     int key_size = 0;
768     void *info_p;
769     int info_size = 0;
770 
771     while (NextDB(dbcp, (char **)&key, &key_size, &info_p, &info_size))
772     {
773         Log(LOG_LEVEL_DEBUG, "Found key persistent class key '%s'", key);
774 
775         /* Info points to db-owned data, which is not aligned properly and
776          * dereferencing might be slow or even cause SIGBUS! */
777         PersistentClassInfo info = { 0 };
778         memcpy(&info, info_p,
779                info_size < (int) sizeof(info) ? info_size : (int) sizeof(info));
780 
781         const char *tags = NULL;
782         if (info_size > (int) sizeof(PersistentClassInfo))
783         {
784             /* This is char pointer, it can point to unaligned data. */
785             tags = ((PersistentClassInfo *) info_p)->tags;
786         }
787         else
788         {
789             tags = "";                                          /* no tags */
790         }
791 
792         if (now > info.expires)
793         {
794             Log(LOG_LEVEL_VERBOSE, "Persistent class '%s' expired", key);
795             DBCursorDeleteEntry(dbcp);
796         }
797         else
798         {
799             Log(LOG_LEVEL_VERBOSE, "Persistent class '%s' for %jd more minutes",
800                 key, (intmax_t) ((info.expires - now) / 60));
801             if ((ctx->negated_classes != NULL) && StringSetContains(ctx->negated_classes, key))
802             {
803                 Log(LOG_LEVEL_VERBOSE,
804                     "Not adding persistent class '%s' due to match in -N/--negate", key);
805             }
806             else
807             {
808                 Log(LOG_LEVEL_DEBUG, "Adding persistent class '%s'", key);
809 
810                 ClassRef ref = ClassRefParse(key);
811                 EvalContextClassPut(ctx, ref.ns, ref.name, true, CONTEXT_SCOPE_NAMESPACE, tags, NULL);
812 
813                 StringSet *tag_set = EvalContextClassTags(ctx, ref.ns, ref.name);
814                 assert(tag_set);
815 
816                 StringSetAdd(tag_set, xstrdup("source=persistent"));
817 
818                 ClassRefDestroy(ref);
819             }
820         }
821     }
822 
823     DeleteDBCursor(dbcp);
824     CloseDB(dbp);
825 }
826 
EvalContextSetNegatedClasses(EvalContext * ctx,StringSet * negated_classes)827 void EvalContextSetNegatedClasses(EvalContext *ctx, StringSet *negated_classes)
828 {
829     assert(ctx != NULL);
830     ctx->negated_classes = negated_classes;
831 }
832 
833 
BundleAbort(EvalContext * ctx)834 bool BundleAbort(EvalContext *ctx)
835 {
836     assert(ctx != NULL);
837     if (ctx->bundle_aborted)
838     {
839         ctx->bundle_aborted = false;
840         return true;
841     }
842 
843     return false;
844 }
845 
BundleAborted(const EvalContext * ctx)846 static bool BundleAborted(const EvalContext* ctx)
847 {
848     assert(ctx != NULL);
849     return ctx->bundle_aborted;
850 }
851 
SetBundleAborted(EvalContext * ctx)852 static void SetBundleAborted(EvalContext *ctx)
853 {
854     assert(ctx != NULL);
855     ctx->bundle_aborted = true;
856 }
857 
SetEvalAborted(EvalContext * ctx)858 static void SetEvalAborted(EvalContext *ctx)
859 {
860     assert(ctx != NULL);
861     ctx->eval_aborted = true;
862 }
863 
EvalAborted(const EvalContext * ctx)864 bool EvalAborted(const EvalContext *ctx)
865 {
866     assert(ctx != NULL);
867     return ctx->eval_aborted;
868 }
869 
EvalContextHeapAddAbort(EvalContext * ctx,const char * context,const char * activated_on_context)870 void EvalContextHeapAddAbort(EvalContext *ctx, const char *context, const char *activated_on_context)
871 {
872     assert(ctx != NULL);
873     if (!IsItemIn(ctx->heap_abort, context))
874     {
875         AppendItem(&ctx->heap_abort, context, activated_on_context);
876     }
877 
878     const char *aborting_context = GetAgentAbortingContext(ctx);
879 
880     if (aborting_context)
881     {
882         Log(LOG_LEVEL_NOTICE, "cf-agent aborted on defined class '%s'", aborting_context);
883         SetEvalAborted(ctx);
884     }
885 }
886 
EvalContextHeapAddAbortCurrentBundle(EvalContext * ctx,const char * context,const char * activated_on_context)887 void EvalContextHeapAddAbortCurrentBundle(EvalContext *ctx, const char *context, const char *activated_on_context)
888 {
889     assert(ctx != NULL);
890     if (!IsItemIn(ctx->heap_abort_current_bundle, context))
891     {
892         AppendItem(&ctx->heap_abort_current_bundle, context, activated_on_context);
893     }
894 }
895 
896 /*****************************************************************************/
897 
MissingDependencies(EvalContext * ctx,const Promise * pp)898 bool MissingDependencies(EvalContext *ctx, const Promise *pp)
899 {
900     assert(ctx != NULL);
901     const Rlist *dependenies = PromiseGetConstraintAsList(ctx, "depends_on", pp);
902     if (RlistIsNullList(dependenies))
903     {
904         return false;
905     }
906 
907     for (const Rlist *rp = PromiseGetConstraintAsList(ctx, "depends_on", pp); rp; rp = rp->next)
908     {
909         if (rp->val.type != RVAL_TYPE_SCALAR)
910         {
911             return true;
912         }
913 
914         if (!StringSetContains(ctx->dependency_handles, RlistScalarValue(rp)))
915         {
916             Log(LOG_LEVEL_VERBOSE, "Skipping promise '%s', as promise dependency '%s' has not yet been kept",
917                     pp->promiser, RlistScalarValue(rp));
918             return true;
919         }
920     }
921 
922     return false;
923 }
924 
StackFrameBundleDestroy(StackFrameBundle frame)925 static void StackFrameBundleDestroy(StackFrameBundle frame)
926 {
927     ClassTableDestroy(frame.classes);
928     VariableTableDestroy(frame.vars);
929 }
930 
StackFrameBodyDestroy(ARG_UNUSED StackFrameBody frame)931 static void StackFrameBodyDestroy(ARG_UNUSED StackFrameBody frame)
932 {
933     VariableTableDestroy(frame.vars);
934 }
935 
StackFramePromiseDestroy(StackFramePromise frame)936 static void StackFramePromiseDestroy(StackFramePromise frame)
937 {
938     VariableTableDestroy(frame.vars);
939 }
940 
StackFramePromiseIterationDestroy(StackFramePromiseIteration frame)941 static void StackFramePromiseIterationDestroy(StackFramePromiseIteration frame)
942 {
943     PromiseDestroy(frame.owner);
944     RingBufferDestroy(frame.log_messages);
945 }
946 
StackFrameDestroy(StackFrame * frame)947 static void StackFrameDestroy(StackFrame *frame)
948 {
949     if (frame)
950     {
951         switch (frame->type)
952         {
953         case STACK_FRAME_TYPE_BUNDLE:
954             StackFrameBundleDestroy(frame->data.bundle);
955             break;
956 
957         case STACK_FRAME_TYPE_BODY:
958             StackFrameBodyDestroy(frame->data.body);
959             break;
960 
961         case STACK_FRAME_TYPE_BUNDLE_SECTION:
962             break;
963 
964         case STACK_FRAME_TYPE_PROMISE:
965             StackFramePromiseDestroy(frame->data.promise);
966             break;
967 
968         case STACK_FRAME_TYPE_PROMISE_ITERATION:
969             StackFramePromiseIterationDestroy(frame->data.promise_iteration);
970             break;
971 
972         default:
973             ProgrammingError("Unhandled stack frame type");
974         }
975 
976         free(frame->path);
977         free(frame);
978     }
979 }
980 
981 static
FreePackageManager(PackageModuleBody * manager)982 void FreePackageManager(PackageModuleBody *manager)
983 {
984     assert(manager != NULL);
985 
986     free(manager->name);
987     free(manager->interpreter);
988     free(manager->module_path);
989     RlistDestroy(manager->options);
990     free(manager);
991 }
992 
993 static
PackagePromiseConfigNew()994 PackagePromiseContext *PackagePromiseConfigNew()
995 {
996     PackagePromiseContext *package_promise_defaults =
997             xmalloc(sizeof(PackagePromiseContext));
998     package_promise_defaults->control_package_module = NULL;
999     package_promise_defaults->control_package_inventory = NULL;
1000     package_promise_defaults->package_modules_bodies =
1001             SeqNew(5, FreePackageManager);
1002 
1003     return package_promise_defaults;
1004 }
1005 
1006 static
FreePackagePromiseContext(PackagePromiseContext * pp_ctx)1007 void FreePackagePromiseContext(PackagePromiseContext *pp_ctx)
1008 {
1009     SeqDestroy(pp_ctx->package_modules_bodies);
1010     RlistDestroy(pp_ctx->control_package_inventory);
1011     free(pp_ctx->control_package_module);
1012     free(pp_ctx);
1013 }
1014 
1015 /* Keeps the last 5 messages of each promise in a ring buffer in the
1016  * EvalContext, which are written to a JSON file from the Enterprise function
1017  * EvalContextLogPromiseIterationOutcome() at the end of each promise. */
MissionPortalLogHook(LoggingPrivContext * pctx,LogLevel level,const char * message)1018 char *MissionPortalLogHook(LoggingPrivContext *pctx, LogLevel level, const char *message)
1019 {
1020     const EvalContext *ctx = pctx->param;
1021 
1022     StackFrame *last_frame = LastStackFrame(ctx, 0);
1023     if (last_frame
1024         && last_frame->type == STACK_FRAME_TYPE_PROMISE_ITERATION
1025         && level <= LOG_LEVEL_INFO)
1026     {
1027         RingBufferAppend(last_frame->data.promise_iteration.log_messages, xstrdup(message));
1028     }
1029     return xstrdup(message);
1030 }
1031 
ENTERPRISE_VOID_FUNC_1ARG_DEFINE_STUB(void,EvalContextSetupMissionPortalLogHook,ARG_UNUSED EvalContext *,ctx)1032 ENTERPRISE_VOID_FUNC_1ARG_DEFINE_STUB(void, EvalContextSetupMissionPortalLogHook,
1033                                       ARG_UNUSED EvalContext *, ctx)
1034 {
1035 }
1036 
EvalContextNew(void)1037 EvalContext *EvalContextNew(void)
1038 {
1039     EvalContext *ctx = xcalloc(1, sizeof(EvalContext));
1040 
1041     ctx->eval_options = EVAL_OPTION_FULL;
1042     ctx->stack = SeqNew(10, StackFrameDestroy);
1043     ctx->global_classes = ClassTableNew();
1044     ctx->global_variables = VariableTableNew();
1045     ctx->match_variables = VariableTableNew();
1046     ctx->dependency_handles = StringSetNew();
1047 
1048     ctx->uid = getuid();
1049     ctx->gid = getgid();
1050     ctx->pid = getpid();
1051 
1052 #ifndef __MINGW32__
1053     ctx->ppid = getppid();
1054 #endif
1055 
1056     ctx->promise_lock_cache = StringSetNew();
1057     ctx->function_cache = FuncCacheMapNew();
1058 
1059     EvalContextSetupMissionPortalLogHook(ctx);
1060 
1061     ctx->package_promise_context = PackagePromiseConfigNew();
1062 
1063     ctx->all_classes = NULL;
1064     ctx->negated_classes = NULL;
1065     ctx->bundle_names = StringSetNew();
1066     ctx->remote_var_promises = NULL;
1067 
1068     ctx->select_end_match_eof = false;
1069 
1070     ctx->dump_reports = false;
1071 
1072     return ctx;
1073 }
1074 
EvalContextDestroy(EvalContext * ctx)1075 void EvalContextDestroy(EvalContext *ctx)
1076 {
1077     if (ctx)
1078     {
1079         free(ctx->launch_directory);
1080         free(ctx->entry_point);
1081 
1082         // Freeing logging context doesn't belong here...
1083         {
1084             LoggingPrivContext *pctx = LoggingPrivGetContext();
1085             free(pctx);
1086             LoggingPrivSetContext(NULL);
1087         }
1088         LoggingFreeCurrentThreadContext();
1089 
1090         EvalContextDeleteIpAddresses(ctx);
1091 
1092         DeleteItemList(ctx->heap_abort);
1093         DeleteItemList(ctx->heap_abort_current_bundle);
1094 
1095         RlistDestroy(ctx->args);
1096 
1097         SeqDestroy(ctx->stack);
1098 
1099         ClassTableDestroy(ctx->global_classes);
1100         VariableTableDestroy(ctx->global_variables);
1101         VariableTableDestroy(ctx->match_variables);
1102 
1103         StringSetDestroy(ctx->dependency_handles);
1104         StringSetDestroy(ctx->promise_lock_cache);
1105 
1106         FuncCacheMapDestroy(ctx->function_cache);
1107 
1108         FreePackagePromiseContext(ctx->package_promise_context);
1109 
1110         StringSetDestroy(ctx->all_classes);
1111         StringSetDestroy(ctx->negated_classes);
1112         StringSetDestroy(ctx->bundle_names);
1113         if (ctx->remote_var_promises != NULL)
1114         {
1115             RemoteVarPromisesMapDestroy(ctx->remote_var_promises);
1116             ctx->remote_var_promises = NULL;
1117         }
1118 
1119         free(ctx);
1120     }
1121 }
1122 
EvalContextHeapContainsSoft(const EvalContext * ctx,const char * ns,const char * name)1123 static bool EvalContextHeapContainsSoft(const EvalContext *ctx, const char *ns, const char *name)
1124 {
1125     const Class *cls = ClassTableGet(ctx->global_classes, ns, name);
1126     return cls && cls->is_soft;
1127 }
1128 
EvalContextHeapContainsHard(const EvalContext * ctx,const char * name)1129 static bool EvalContextHeapContainsHard(const EvalContext *ctx, const char *name)
1130 {
1131     const Class *cls = ClassTableGet(ctx->global_classes, NULL, name);
1132     return cls && !cls->is_soft;
1133 }
1134 
StackFrameContainsSoftRecursive(const EvalContext * ctx,const char * context,size_t stack_index)1135 bool StackFrameContainsSoftRecursive(const EvalContext *ctx, const char *context, size_t stack_index)
1136 {
1137     StackFrame *frame = SeqAt(ctx->stack, stack_index);
1138     if (frame->type == STACK_FRAME_TYPE_BUNDLE && ClassTableGet(frame->data.bundle.classes, frame->data.bundle.owner->ns, context) != NULL)
1139     {
1140         return true;
1141     }
1142     else if (stack_index > 0 && frame->inherits_previous)
1143     {
1144         return StackFrameContainsSoftRecursive(ctx, context, stack_index - 1);
1145     }
1146     else
1147     {
1148         return false;
1149     }
1150 }
1151 
EvalContextStackFrameContainsSoft(const EvalContext * ctx,const char * context)1152 static bool EvalContextStackFrameContainsSoft(const EvalContext *ctx, const char *context)
1153 {
1154     if (SeqLength(ctx->stack) == 0)
1155     {
1156         return false;
1157     }
1158 
1159     size_t stack_index = SeqLength(ctx->stack) - 1;
1160     return StackFrameContainsSoftRecursive(ctx, context, stack_index);
1161 }
1162 
EvalContextHeapRemoveSoft(EvalContext * ctx,const char * ns,const char * name)1163 bool EvalContextHeapRemoveSoft(EvalContext *ctx, const char *ns, const char *name)
1164 {
1165     return ClassTableRemove(ctx->global_classes, ns, name);
1166 }
1167 
EvalContextHeapRemoveHard(EvalContext * ctx,const char * name)1168 bool EvalContextHeapRemoveHard(EvalContext *ctx, const char *name)
1169 {
1170     return ClassTableRemove(ctx->global_classes, NULL, name);
1171 }
1172 
EvalContextClear(EvalContext * ctx)1173 void EvalContextClear(EvalContext *ctx)
1174 {
1175     ClassTableClear(ctx->global_classes);
1176     EvalContextDeleteIpAddresses(ctx);
1177     VariableTableClear(ctx->global_variables, NULL, NULL, NULL);
1178     VariableTableClear(ctx->match_variables, NULL, NULL, NULL);
1179     StringSetClear(ctx->promise_lock_cache);
1180     SeqClear(ctx->stack);
1181     FuncCacheMapClear(ctx->function_cache);
1182 }
1183 
EvalContextGetPromiseCallerMethods(EvalContext * ctx)1184 Rlist *EvalContextGetPromiseCallerMethods(EvalContext *ctx) {
1185     Rlist *callers_promisers = NULL;
1186 
1187     for (size_t i = 0; i < SeqLength(ctx->stack); i++)
1188     {
1189         StackFrame *frame = SeqAt(ctx->stack, i);
1190         switch (frame->type)
1191         {
1192         case STACK_FRAME_TYPE_BODY:
1193             break;
1194 
1195         case STACK_FRAME_TYPE_BUNDLE:
1196             break;
1197 
1198         case STACK_FRAME_TYPE_PROMISE_ITERATION:
1199             break;
1200 
1201         case STACK_FRAME_TYPE_PROMISE:
1202             if (strcmp(frame->data.promise.owner->parent_section->promise_type, "methods") == 0) {
1203                 RlistAppendScalar(&callers_promisers, frame->data.promise.owner->promiser);
1204             }
1205             break;
1206 
1207         case STACK_FRAME_TYPE_BUNDLE_SECTION:
1208             break;
1209 
1210         default:
1211             ProgrammingError("Unhandled stack frame type");
1212         }
1213     }
1214     return callers_promisers;
1215 }
1216 
EvalContextGetPromiseCallers(EvalContext * ctx)1217 JsonElement *EvalContextGetPromiseCallers(EvalContext *ctx) {
1218     JsonElement *callers = JsonArrayCreate(4);
1219     size_t depth = SeqLength(ctx->stack);
1220 
1221     for (size_t i = 0; i < depth; i++)
1222     {
1223         StackFrame *frame = SeqAt(ctx->stack, i);
1224         JsonElement *f = JsonObjectCreate(10);
1225         JsonObjectAppendInteger(f, "frame", depth-i);
1226         JsonObjectAppendInteger(f, "depth", i);
1227 
1228         switch (frame->type)
1229         {
1230         case STACK_FRAME_TYPE_BODY:
1231             JsonObjectAppendString(f, "type", "body");
1232             JsonObjectAppendObject(f, "body", BodyToJson(frame->data.body.owner));
1233             break;
1234 
1235         case STACK_FRAME_TYPE_BUNDLE:
1236             JsonObjectAppendString(f, "type", "bundle");
1237             JsonObjectAppendObject(f, "bundle", BundleToJson(frame->data.bundle.owner));
1238             break;
1239 
1240         case STACK_FRAME_TYPE_PROMISE_ITERATION:
1241             JsonObjectAppendString(f, "type", "iteration");
1242             JsonObjectAppendInteger(f, "iteration_index", frame->data.promise_iteration.index);
1243 
1244             break;
1245 
1246         case STACK_FRAME_TYPE_PROMISE:
1247             JsonObjectAppendString(f, "type", "promise");
1248             JsonObjectAppendString(f, "promise_type", frame->data.promise.owner->parent_section->promise_type);
1249             JsonObjectAppendString(f, "promiser", frame->data.promise.owner->promiser);
1250             JsonObjectAppendString(f, "promise_classes", frame->data.promise.owner->classes);
1251             JsonObjectAppendString(f, "promise_comment",
1252                                    (frame->data.promise.owner->comment == NULL) ? "" : frame->data.promise.owner->comment);
1253             break;
1254 
1255         case STACK_FRAME_TYPE_BUNDLE_SECTION:
1256             JsonObjectAppendString(f, "type", "promise_type");
1257             JsonObjectAppendString(f, "promise_type", frame->data.bundle_section.owner->promise_type);
1258             break;
1259 
1260         default:
1261             ProgrammingError("Unhandled stack frame type");
1262         }
1263 
1264         JsonArrayAppendObject(callers, f);
1265     }
1266 
1267     return callers;
1268 }
1269 
EvalContextSetBundleArgs(EvalContext * ctx,const Rlist * args)1270 void EvalContextSetBundleArgs(EvalContext *ctx, const Rlist *args)
1271 {
1272     if (ctx->args)
1273     {
1274         RlistDestroy(ctx->args);
1275     }
1276 
1277     ctx->args = RlistCopy(args);
1278 }
1279 
EvalContextGetBundleArgs(EvalContext * ctx)1280 Rlist *EvalContextGetBundleArgs(EvalContext *ctx)
1281 {
1282     return (Rlist *) ctx->args;
1283 }
1284 
EvalContextSetPass(EvalContext * ctx,int pass)1285 void EvalContextSetPass(EvalContext *ctx, int pass)
1286 {
1287     ctx->pass = pass;
1288 }
1289 
EvalContextGetPass(EvalContext * ctx)1290 int EvalContextGetPass(EvalContext *ctx)
1291 {
1292     return ctx->pass;
1293 }
1294 
StackFrameNew(StackFrameType type,bool inherit_previous)1295 static StackFrame *StackFrameNew(StackFrameType type, bool inherit_previous)
1296 {
1297     StackFrame *frame = xmalloc(sizeof(StackFrame));
1298 
1299     frame->type = type;
1300     frame->inherits_previous = inherit_previous;
1301     frame->path = NULL;
1302 
1303     return frame;
1304 }
1305 
StackFrameNewBundle(const Bundle * owner,bool inherit_previous)1306 static StackFrame *StackFrameNewBundle(const Bundle *owner, bool inherit_previous)
1307 {
1308     StackFrame *frame = StackFrameNew(STACK_FRAME_TYPE_BUNDLE, inherit_previous);
1309 
1310     frame->data.bundle.owner = owner;
1311     frame->data.bundle.classes = ClassTableNew();
1312     frame->data.bundle.vars = VariableTableNew();
1313 
1314     return frame;
1315 }
1316 
StackFrameNewBody(const Body * owner)1317 static StackFrame *StackFrameNewBody(const Body *owner)
1318 {
1319     StackFrame *frame = StackFrameNew(STACK_FRAME_TYPE_BODY, false);
1320 
1321     frame->data.body.owner = owner;
1322     frame->data.body.vars = VariableTableNew();
1323 
1324     return frame;
1325 }
1326 
StackFrameNewBundleSection(const BundleSection * owner)1327 static StackFrame *StackFrameNewBundleSection(const BundleSection *owner)
1328 {
1329     StackFrame *frame = StackFrameNew(STACK_FRAME_TYPE_BUNDLE_SECTION, true);
1330 
1331     frame->data.bundle_section.owner = owner;
1332 
1333     return frame;
1334 }
1335 
StackFrameNewPromise(const Promise * owner)1336 static StackFrame *StackFrameNewPromise(const Promise *owner)
1337 {
1338     StackFrame *frame = StackFrameNew(STACK_FRAME_TYPE_PROMISE, true);
1339 
1340     frame->data.promise.owner = owner;
1341 
1342     return frame;
1343 }
1344 
StackFrameNewPromiseIteration(Promise * owner,const PromiseIterator * iter_ctx)1345 static StackFrame *StackFrameNewPromiseIteration(Promise *owner, const PromiseIterator *iter_ctx)
1346 {
1347     StackFrame *frame = StackFrameNew(STACK_FRAME_TYPE_PROMISE_ITERATION, true);
1348 
1349     frame->data.promise_iteration.owner = owner;
1350     frame->data.promise_iteration.iter_ctx = iter_ctx;
1351     frame->data.promise_iteration.log_messages = RingBufferNew(5, NULL, free);
1352 
1353     return frame;
1354 }
1355 
EvalContextStackFrameRemoveSoft(EvalContext * ctx,const char * context)1356 void EvalContextStackFrameRemoveSoft(EvalContext *ctx, const char *context)
1357 {
1358     StackFrame *frame = LastStackFrameByType(ctx, STACK_FRAME_TYPE_BUNDLE);
1359     assert(frame);
1360 
1361     ClassTableRemove(frame->data.bundle.classes, frame->data.bundle.owner->ns, context);
1362 }
1363 
EvalContextStackPushFrame(EvalContext * ctx,StackFrame * frame)1364 static void EvalContextStackPushFrame(EvalContext *ctx, StackFrame *frame)
1365 {
1366     StackFrame *last_frame = LastStackFrame(ctx, 0);
1367     if (last_frame)
1368     {
1369         if (last_frame->type == STACK_FRAME_TYPE_PROMISE_ITERATION)
1370         {
1371             LogLevel global_log_level = LogGetGlobalLevel();
1372             LogLevel system_log_level = LogGetGlobalSystemLogLevel();
1373             LoggingPrivSetLevels(system_log_level != LOG_LEVEL_NOTHING ? system_log_level : global_log_level,
1374                                  global_log_level);
1375         }
1376     }
1377 
1378     SeqAppend(ctx->stack, frame);
1379 
1380     assert(!frame->path);
1381     frame->path = EvalContextStackPath(ctx);
1382 
1383     LogDebug(LOG_MOD_EVALCTX, "PUSHED FRAME (type %s)",
1384              STACK_FRAME_TYPE_STR[frame->type]);
1385 }
1386 
EvalContextStackPushBundleFrame(EvalContext * ctx,const Bundle * owner,const Rlist * args,bool inherits_previous)1387 void EvalContextStackPushBundleFrame(EvalContext *ctx, const Bundle *owner, const Rlist *args, bool inherits_previous)
1388 {
1389     assert(!LastStackFrame(ctx, 0) || LastStackFrame(ctx, 0)->type == STACK_FRAME_TYPE_PROMISE_ITERATION);
1390 
1391     EvalContextStackPushFrame(ctx, StackFrameNewBundle(owner, inherits_previous));
1392 
1393     if (RlistLen(args) > 0)
1394     {
1395         const Promise *caller = EvalContextStackCurrentPromise(ctx);
1396         if (caller)
1397         {
1398             VariableTable *table = LastStackFrameByType(ctx, STACK_FRAME_TYPE_BUNDLE)->data.bundle.vars;
1399             VariableTableClear(table, NULL, NULL, NULL);
1400         }
1401 
1402         ScopeAugment(ctx, owner, caller, args);
1403     }
1404 
1405     {
1406         VariableTableIterator *iter = VariableTableIteratorNew(ctx->global_variables, owner->ns, owner->name, NULL);
1407         Variable *var = NULL;
1408         while ((var = VariableTableIteratorNext(iter)))
1409         {
1410             Rval var_rval = VariableGetRval(var, true);
1411             Rval retval = ExpandPrivateRval(ctx, owner->ns, owner->name, var_rval.item, var_rval.type);
1412             VariableSetRval(var, retval);
1413         }
1414         VariableTableIteratorDestroy(iter);
1415     }
1416 }
1417 
EvalContextStackPushBodyFrame(EvalContext * ctx,const Promise * caller,const Body * body,const Rlist * args)1418 void EvalContextStackPushBodyFrame(EvalContext *ctx, const Promise *caller, const Body *body, const Rlist *args)
1419 {
1420 #ifndef NDEBUG
1421     StackFrame *last_frame = LastStackFrame(ctx, 0);
1422     if (last_frame)
1423     {
1424         assert(last_frame->type == STACK_FRAME_TYPE_BUNDLE_SECTION);
1425     }
1426     else
1427     {
1428         assert(strcmp("control", body->name) == 0);
1429     }
1430 #endif
1431 
1432 
1433     EvalContextStackPushFrame(ctx, StackFrameNewBody(body));
1434 
1435     if (RlistLen(body->args) != RlistLen(args))
1436     {
1437         if (caller)
1438         {
1439             Log(LOG_LEVEL_ERR, "Argument arity mismatch in body '%s' at line %zu in file '%s', expected %d, got %d",
1440                 body->name, caller->offset.line, PromiseGetBundle(caller)->source_path, RlistLen(body->args), RlistLen(args));
1441         }
1442         else
1443         {
1444             assert(strcmp("control", body->name) == 0);
1445             ProgrammingError("Control body stack frame was pushed with arguments. This should have been caught before");
1446         }
1447         return;
1448     }
1449     else
1450     {
1451         ScopeMapBodyArgs(ctx, body, args);
1452     }
1453 }
1454 
EvalContextStackPushBundleSectionFrame(EvalContext * ctx,const BundleSection * owner)1455 void EvalContextStackPushBundleSectionFrame(EvalContext *ctx, const BundleSection *owner)
1456 {
1457     assert(LastStackFrame(ctx, 0) && LastStackFrame(ctx, 0)->type == STACK_FRAME_TYPE_BUNDLE);
1458 
1459     StackFrame *frame = StackFrameNewBundleSection(owner);
1460     EvalContextStackPushFrame(ctx, frame);
1461 }
1462 
EvalContextStackPushPromiseFrame(EvalContext * ctx,const Promise * owner)1463 void EvalContextStackPushPromiseFrame(EvalContext *ctx, const Promise *owner)
1464 {
1465     assert(LastStackFrame(ctx, 0));
1466     assert(LastStackFrame(ctx, 0)->type == STACK_FRAME_TYPE_BUNDLE_SECTION);
1467 
1468     EvalContextVariableClearMatch(ctx);
1469 
1470     StackFrame *frame = StackFrameNewPromise(owner);
1471 
1472     EvalContextStackPushFrame(ctx, frame);
1473 
1474     // create an empty table
1475     frame->data.promise.vars = VariableTableNew();
1476 
1477     if (PromiseGetBundle(owner)->source_path)
1478     {
1479         char path[CF_BUFSIZE];
1480         if (!IsAbsoluteFileName(PromiseGetBundle(owner)->source_path) && ctx->launch_directory)
1481         {
1482             snprintf(path, CF_BUFSIZE, "%s%c%s", ctx->launch_directory, FILE_SEPARATOR, PromiseGetBundle(owner)->source_path);
1483         }
1484         else
1485         {
1486             strlcpy(path, PromiseGetBundle(owner)->source_path, CF_BUFSIZE);
1487         }
1488 
1489         EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "promise_filename", path, CF_DATA_TYPE_STRING, "source=promise");
1490 
1491         // We now make path just the directory name!
1492         DeleteSlash(path);
1493         ChopLastNode(path);
1494 
1495         EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "promise_dirname", path, CF_DATA_TYPE_STRING, "source=promise");
1496         char number[PRINTSIZE(uintmax_t)];
1497         xsnprintf(number, CF_SMALLBUF, "%ju", (uintmax_t) owner->offset.line);
1498         EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "promise_linenumber", number, CF_DATA_TYPE_STRING, "source=promise");
1499     }
1500 
1501     char v[PRINTSIZE(int)];
1502     xsnprintf(v, sizeof(v), "%d", (int) ctx->uid);
1503     EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "promiser_uid", v, CF_DATA_TYPE_INT, "source=agent");
1504     xsnprintf(v, sizeof(v), "%d", (int) ctx->gid);
1505     EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "promiser_gid", v, CF_DATA_TYPE_INT, "source=agent");
1506     xsnprintf(v, sizeof(v), "%d", (int) ctx->pid);
1507     EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "promiser_pid", v, CF_DATA_TYPE_INT, "source=agent");
1508     xsnprintf(v, sizeof(v), "%d", (int) ctx->ppid);
1509     EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "promiser_ppid", v, CF_DATA_TYPE_INT, "source=agent");
1510 
1511     EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "bundle", PromiseGetBundle(owner)->name, CF_DATA_TYPE_STRING, "source=promise");
1512     EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "namespace", PromiseGetNamespace(owner), CF_DATA_TYPE_STRING, "source=promise");
1513 
1514     // Recompute `with`
1515     for (size_t i = 0; i < SeqLength(owner->conlist); i++)
1516     {
1517         Constraint *cp = SeqAt(owner->conlist, i);
1518         if (StringEqual(cp->lval, "with"))
1519         {
1520             Rval final = EvaluateFinalRval(ctx, PromiseGetPolicy(owner), NULL,
1521                                            "this", cp->rval, false, owner);
1522             if (final.type == RVAL_TYPE_SCALAR &&
1523                 ((EvalContextGetPass(ctx) == CF_DONEPASSES - 1) || !IsCf3VarString(RvalScalarValue(final))))
1524             {
1525                 EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "with", RvalScalarValue(final), CF_DATA_TYPE_STRING, "source=promise_iteration/with");
1526             }
1527             RvalDestroy(final);
1528         }
1529     }
1530 }
1531 
EvalContextStackPushPromiseIterationFrame(EvalContext * ctx,const PromiseIterator * iter_ctx)1532 Promise *EvalContextStackPushPromiseIterationFrame(EvalContext *ctx, const PromiseIterator *iter_ctx)
1533 {
1534     const StackFrame *last_frame = LastStackFrame(ctx, 0);
1535 
1536     assert(last_frame       != NULL);
1537     assert(last_frame->type == STACK_FRAME_TYPE_PROMISE);
1538 
1539     /* Evaluate all constraints by calling functions etc. */
1540     bool excluded;
1541     Promise *pexp = ExpandDeRefPromise(ctx, last_frame->data.promise.owner,
1542                                        &excluded);
1543     if (excluded || !pexp)
1544     {
1545         PromiseDestroy(pexp);
1546         return NULL;
1547     }
1548 
1549     EvalContextStackPushFrame(ctx, StackFrameNewPromiseIteration(pexp, iter_ctx));
1550     LoggingPrivSetLevels(CalculateLogLevel(pexp), CalculateReportLevel(pexp));
1551 
1552     return pexp;
1553 }
1554 
EvalContextStackPopFrame(EvalContext * ctx)1555 void EvalContextStackPopFrame(EvalContext *ctx)
1556 {
1557     assert(SeqLength(ctx->stack) > 0);
1558 
1559     StackFrame *last_frame = LastStackFrame(ctx, 0);
1560     StackFrameType last_frame_type = last_frame->type;
1561 
1562     switch (last_frame_type)
1563     {
1564     case STACK_FRAME_TYPE_BUNDLE:
1565         {
1566             const Bundle *bp = last_frame->data.bundle.owner;
1567             if (strcmp(bp->type, "edit_line") == 0 || strcmp(bp->type, "edit_xml") == 0)
1568             {
1569                 VariableTableClear(last_frame->data.bundle.vars, "default", "edit", NULL);
1570             }
1571         }
1572         break;
1573 
1574     case STACK_FRAME_TYPE_PROMISE_ITERATION:
1575         {
1576             LogLevel global_log_level = LogGetGlobalLevel();
1577             LogLevel system_log_level = LogGetGlobalSystemLogLevel();
1578             LoggingPrivSetLevels(system_log_level != LOG_LEVEL_NOTHING ? system_log_level : global_log_level,
1579                                  global_log_level);
1580         }
1581         break;
1582 
1583     default:
1584         break;
1585     }
1586 
1587     SeqRemove(ctx->stack, SeqLength(ctx->stack) - 1);
1588 
1589     last_frame = LastStackFrame(ctx, 0);
1590     if (last_frame)
1591     {
1592         if (last_frame->type == STACK_FRAME_TYPE_PROMISE_ITERATION)
1593         {
1594             const Promise *pp = EvalContextStackCurrentPromise(ctx);
1595             LoggingPrivSetLevels(CalculateLogLevel(pp), CalculateReportLevel(pp));
1596         }
1597     }
1598 
1599     LogDebug(LOG_MOD_EVALCTX, "POPPED FRAME (type %s)",
1600              STACK_FRAME_TYPE_STR[last_frame_type]);
1601 }
1602 
EvalContextClassRemove(EvalContext * ctx,const char * ns,const char * name)1603 bool EvalContextClassRemove(EvalContext *ctx, const char *ns, const char *name)
1604 {
1605     for (size_t i = 0; i < SeqLength(ctx->stack); i++)
1606     {
1607         StackFrame *frame = SeqAt(ctx->stack, i);
1608         if (frame->type != STACK_FRAME_TYPE_BUNDLE)
1609         {
1610             continue;
1611         }
1612 
1613         ClassTableRemove(frame->data.bundle.classes, ns, name);
1614     }
1615 
1616     return ClassTableRemove(ctx->global_classes, ns, name);
1617 }
1618 
EvalContextClassGet(const EvalContext * ctx,const char * ns,const char * name)1619 Class *EvalContextClassGet(const EvalContext *ctx, const char *ns, const char *name)
1620 {
1621     StackFrame *frame = LastStackFrameByType(ctx, STACK_FRAME_TYPE_BUNDLE);
1622     if (frame)
1623     {
1624         Class *cls = ClassTableGet(frame->data.bundle.classes, ns, name);
1625         if (cls)
1626         {
1627             return cls;
1628         }
1629     }
1630 
1631     return ClassTableGet(ctx->global_classes, ns, name);
1632 }
1633 
EvalContextClassMatch(const EvalContext * ctx,const char * regex)1634 Class *EvalContextClassMatch(const EvalContext *ctx, const char *regex)
1635 {
1636     StackFrame *frame = LastStackFrameByType(ctx, STACK_FRAME_TYPE_BUNDLE);
1637     if (frame)
1638     {
1639         Class *cls = ClassTableMatch(frame->data.bundle.classes, regex);
1640         if (cls)
1641         {
1642             return cls;
1643         }
1644     }
1645 
1646     return ClassTableMatch(ctx->global_classes, regex);
1647 }
1648 
EvalContextClassPutTagsSet(EvalContext * ctx,const char * ns,const char * name,bool is_soft,ContextScope scope,StringSet * tags,const char * comment)1649 static bool EvalContextClassPutTagsSet(EvalContext *ctx, const char *ns, const char *name, bool is_soft,
1650                                        ContextScope scope, StringSet *tags, const char *comment)
1651 {
1652     {
1653         char context_copy[2 * CF_MAXVARSIZE];
1654         char canonified_context[CF_MAXVARSIZE];
1655 
1656 
1657         /* Redmine #7013
1658          * Fix for classes names longer than CF_MAXVARSIZE. */
1659         if (strlen(name) >= sizeof(canonified_context))
1660         {
1661             Log(LOG_LEVEL_WARNING, "Skipping adding class [%s] as its name "
1662                 "is equal or longer than %zu", name, sizeof(canonified_context));
1663             return false;
1664         }
1665 
1666         strlcpy(canonified_context, name, sizeof(canonified_context));
1667 
1668         if (Chop(canonified_context, CF_EXPANDSIZE) == -1)
1669         {
1670             Log(LOG_LEVEL_ERR, "Chop was called on a string that seemed to have no terminator");
1671         }
1672         CanonifyNameInPlace(canonified_context);
1673 
1674         if (ns && strcmp(ns, "default") != 0)
1675         {
1676             snprintf(context_copy, sizeof(context_copy), "%s:%s", ns, canonified_context);
1677         }
1678         else
1679         {
1680             strlcpy(context_copy, canonified_context, sizeof(context_copy));
1681         }
1682 
1683         if (strlen(context_copy) == 0)
1684         {
1685             return false;
1686         }
1687 
1688         if (IsRegexItemIn(ctx, ctx->heap_abort_current_bundle, context_copy))
1689         {
1690             const Bundle *bundle = EvalContextStackCurrentBundle(ctx);
1691             if (bundle != NULL)
1692             {
1693                 Log(LOG_LEVEL_ERR, "Bundle '%s' aborted on defined class '%s'", bundle->name, context_copy);
1694             }
1695             else
1696             {
1697                 Log(LOG_LEVEL_ERR, "Bundle (unknown) aborted on defined class '%s'", context_copy);
1698             }
1699             SetBundleAborted(ctx);
1700         }
1701 
1702         if (IsRegexItemIn(ctx, ctx->heap_abort, context_copy))
1703         {
1704             Log(LOG_LEVEL_NOTICE, "cf-agent aborted on defined class '%s'", context_copy);
1705             SetEvalAborted(ctx);
1706         }
1707     }
1708 
1709     Class *existing_class = EvalContextClassGet(ctx, ns, name);
1710     if (existing_class && existing_class->scope == scope)
1711     {
1712         return false;
1713     }
1714 
1715     Nova_ClassHistoryAddContextName(ctx->all_classes, name);
1716 
1717     switch (scope)
1718     {
1719     case CONTEXT_SCOPE_BUNDLE:
1720         {
1721             StackFrame *frame = LastStackFrameByType(ctx, STACK_FRAME_TYPE_BUNDLE);
1722             if (!frame)
1723             {
1724                 ProgrammingError("Attempted to add bundle class '%s' while not evaluating a bundle", name);
1725             }
1726             ClassTablePut(frame->data.bundle.classes, ns, name, is_soft, scope, tags, comment);
1727         }
1728         break;
1729 
1730     case CONTEXT_SCOPE_NAMESPACE:
1731         ClassTablePut(ctx->global_classes, ns, name, is_soft, scope, tags, comment);
1732         break;
1733 
1734     case CONTEXT_SCOPE_NONE:
1735         ProgrammingError("Attempted to add a class without a set scope");
1736     }
1737 
1738     if (!BundleAborted(ctx))
1739     {
1740         for (const Item *ip = ctx->heap_abort_current_bundle; ip != NULL; ip = ip->next)
1741         {
1742             const char *class_expr = ip->name;
1743 
1744             if (IsDefinedClass(ctx, class_expr))
1745             {
1746                 Log(LOG_LEVEL_ERR, "Setting abort for '%s' when setting class '%s'", ip->name, name);
1747                 SetBundleAborted(ctx);
1748                 break;
1749             }
1750         }
1751     }
1752 
1753     return true;
1754 }
1755 
EvalContextClassPut(EvalContext * ctx,const char * ns,const char * name,bool is_soft,ContextScope scope,const char * tags,const char * comment)1756 static bool EvalContextClassPut(EvalContext *ctx, const char *ns, const char *name, bool is_soft,
1757                                 ContextScope scope, const char *tags, const char *comment)
1758 {
1759     StringSet *tags_set = (NULL_OR_EMPTY(tags) ? NULL : StringSetFromString(tags, ','));
1760     bool ret = EvalContextClassPutTagsSet(ctx, ns, name, is_soft, scope, tags_set, comment);
1761     if (!ret)
1762     {
1763         StringSetDestroy(tags_set);
1764     }
1765     return ret;
1766 }
1767 
EvalContextCurrentNamespace(const EvalContext * ctx)1768 static const char *EvalContextCurrentNamespace(const EvalContext *ctx)
1769 {
1770     size_t i = SeqLength(ctx->stack);
1771     while (i > 0)
1772     {
1773         i--;
1774         StackFrame *frame = SeqAt(ctx->stack, i);
1775         switch (frame->type)
1776         {
1777         case STACK_FRAME_TYPE_BUNDLE:
1778             return frame->data.bundle.owner->ns;
1779         case STACK_FRAME_TYPE_BODY:
1780             return frame->data.body.owner->ns;
1781         default:
1782             break; /* out of the switch but not the loop ! */
1783         }
1784     }
1785 
1786     return NULL;
1787 }
1788 
EvalContextClassPutHard(EvalContext * ctx,const char * name,const char * tags)1789 bool EvalContextClassPutHard(EvalContext *ctx, const char *name, const char *tags)
1790 {
1791     return EvalContextClassPut(ctx, NULL, name, false, CONTEXT_SCOPE_NAMESPACE, tags, NULL);
1792 }
1793 
EvalContextClassPutSoft(EvalContext * ctx,const char * name,ContextScope scope,const char * tags)1794 bool EvalContextClassPutSoft(EvalContext *ctx, const char *name, ContextScope scope, const char *tags)
1795 {
1796     StringSet *tags_set = (NULL_OR_EMPTY(tags) ? NULL : StringSetFromString(tags, ','));
1797     bool ret = EvalContextClassPutSoftTagsSet(ctx, name, scope, tags_set);
1798     if (!ret)
1799     {
1800         StringSetDestroy(tags_set);
1801     }
1802     return ret;
1803 }
1804 
EvalContextClassPutSoftTagsSet(EvalContext * ctx,const char * name,ContextScope scope,StringSet * tags)1805 bool EvalContextClassPutSoftTagsSet(EvalContext *ctx, const char *name, ContextScope scope, StringSet *tags)
1806 {
1807     return EvalContextClassPutSoftTagsSetWithComment(ctx, name, scope, tags, NULL);
1808 }
1809 
EvalContextClassPutSoftTagsSetWithComment(EvalContext * ctx,const char * name,ContextScope scope,StringSet * tags,const char * comment)1810 bool EvalContextClassPutSoftTagsSetWithComment(EvalContext *ctx, const char *name, ContextScope scope,
1811                                                StringSet *tags, const char *comment)
1812 {
1813     bool ret;
1814     char *ns = NULL;
1815     char *delim = strchr(name, ':');
1816 
1817     if (delim)
1818     {
1819         ns = xstrndup(name, delim - name);
1820     }
1821 
1822     ret = EvalContextClassPutTagsSet(ctx, ns ? ns : EvalContextCurrentNamespace(ctx),
1823                                      ns ? delim + 1 : name, true, scope, tags, comment);
1824     free(ns);
1825     return ret;
1826 }
1827 
EvalContextClassPutSoftNS(EvalContext * ctx,const char * ns,const char * name,ContextScope scope,const char * tags)1828 bool EvalContextClassPutSoftNS(EvalContext *ctx, const char *ns, const char *name,
1829                                ContextScope scope, const char *tags)
1830 {
1831     return EvalContextClassPut(ctx, ns, name, true, scope, tags, NULL);
1832 }
1833 
1834 /**
1835  * Takes over #tags in case of success.
1836  */
EvalContextClassPutSoftNSTagsSet(EvalContext * ctx,const char * ns,const char * name,ContextScope scope,StringSet * tags)1837 bool EvalContextClassPutSoftNSTagsSet(EvalContext *ctx, const char *ns, const char *name,
1838                                       ContextScope scope, StringSet *tags)
1839 {
1840     return EvalContextClassPutSoftNSTagsSetWithComment(ctx, ns, name, scope, tags, NULL);
1841 }
1842 
EvalContextClassPutSoftNSTagsSetWithComment(EvalContext * ctx,const char * ns,const char * name,ContextScope scope,StringSet * tags,const char * comment)1843 bool EvalContextClassPutSoftNSTagsSetWithComment(EvalContext *ctx, const char *ns, const char *name,
1844                                                  ContextScope scope, StringSet *tags, const char *comment)
1845 {
1846     return EvalContextClassPutTagsSet(ctx, ns, name, true, scope, tags, comment);
1847 }
1848 
EvalContextClassTableIteratorNewGlobal(const EvalContext * ctx,const char * ns,bool is_hard,bool is_soft)1849 ClassTableIterator *EvalContextClassTableIteratorNewGlobal(const EvalContext *ctx, const char *ns, bool is_hard, bool is_soft)
1850 {
1851     return ClassTableIteratorNew(ctx->global_classes, ns, is_hard, is_soft);
1852 }
1853 
EvalContextClassTableIteratorNewLocal(const EvalContext * ctx)1854 ClassTableIterator *EvalContextClassTableIteratorNewLocal(const EvalContext *ctx)
1855 {
1856     StackFrame *frame = LastStackFrameByType(ctx, STACK_FRAME_TYPE_BUNDLE);
1857     if (!frame)
1858     {
1859         return NULL;
1860     }
1861 
1862     return ClassTableIteratorNew(frame->data.bundle.classes, frame->data.bundle.owner->ns, false, true);
1863 }
1864 
EvalContextStackCurrentPromise(const EvalContext * ctx)1865 const Promise *EvalContextStackCurrentPromise(const EvalContext *ctx)
1866 {
1867     StackFrame *frame = LastStackFrameByType(ctx, STACK_FRAME_TYPE_PROMISE_ITERATION);
1868     return frame ? frame->data.promise_iteration.owner : NULL;
1869 }
1870 
EvalContextStackCurrentBundle(const EvalContext * ctx)1871 const Bundle *EvalContextStackCurrentBundle(const EvalContext *ctx)
1872 {
1873     StackFrame *frame = LastStackFrameByType(ctx, STACK_FRAME_TYPE_BUNDLE);
1874     return frame ? frame->data.bundle.owner : NULL;
1875 }
1876 
EvalContextStackCurrentMessages(const EvalContext * ctx)1877 const RingBuffer *EvalContextStackCurrentMessages(const EvalContext *ctx)
1878 {
1879     StackFrame *frame = LastStackFrameByType(ctx, STACK_FRAME_TYPE_PROMISE_ITERATION);
1880     return frame ? frame->data.promise_iteration.log_messages : NULL;
1881 }
1882 
1883 
1884 
1885 /**
1886  * @brief Concatenate string #str to #buf, replacing mangling
1887  *        characters '*' and '#' with their visible counterparts.
1888  */
BufferAppendPromiseStr(Buffer * buf,const char * str)1889 static void BufferAppendPromiseStr(Buffer *buf, const char *str)
1890 {
1891     for (const char *ch = str; *ch != '\0'; ch++)
1892     {
1893         switch (*ch)
1894         {
1895         case CF_MANGLED_NS:
1896             BufferAppendChar(buf, ':');
1897             break;
1898 
1899         case CF_MANGLED_SCOPE:
1900             BufferAppendChar(buf, '.');
1901             break;
1902 
1903         default:
1904             BufferAppendChar(buf, *ch);
1905             break;
1906         }
1907     }
1908 }
1909 
1910 /**
1911  * @brief Like @c BufferAppendPromiseStr, but if @c str contains newlines
1912  *   and is longer than 2*N+3, then only copy an abbreviated version
1913  *   consisting of the first and last N characters, separated by @c `...`
1914  *
1915  * @param buffer Buffer to be used.
1916  * @param promiser Constant string to append
1917  * @param N      Max. length of initial/final segment of @c promiser to keep
1918  * @note 2*N+3 is the maximum length of the appended string (excl. terminating NULL)
1919  *
1920  */
BufferAppendAbbreviatedStr(Buffer * buf,const char * promiser,const int N)1921 static void BufferAppendAbbreviatedStr(Buffer *buf,
1922                                        const char *promiser, const int N)
1923 {
1924     /* check if `promiser` contains a new line (may happen for "insert_lines") */
1925     const char *const nl = strchr(promiser, '\n');
1926     if (NULL == nl)
1927     {
1928         BufferAppendPromiseStr(buf, promiser);
1929     }
1930     else
1931     {
1932         /* `promiser` contains a newline: abbreviate it by taking the first and last few characters */
1933         static const char sep[] = "...";
1934         char abbr[sizeof(sep) + 2 * N];
1935         const int head = (nl > promiser + N) ? N : (nl - promiser);
1936         const char * last_line = strrchr(promiser, '\n') + 1;
1937         assert(last_line); /* not NULL, we know we have at least one '\n' */
1938         const int tail = strlen(last_line);
1939         if (tail > N)
1940         {
1941             last_line += tail - N;
1942         }
1943         memcpy(abbr, promiser, head);
1944         strcpy(abbr + head, sep);
1945         strcat(abbr, last_line);
1946         BufferAppendPromiseStr(buf, abbr);
1947     }
1948 }
1949 
EvalContextStackPath(const EvalContext * ctx)1950 char *EvalContextStackPath(const EvalContext *ctx)
1951 {
1952     Buffer *path = BufferNew();
1953 
1954     for (size_t i = 0; i < SeqLength(ctx->stack); i++)
1955     {
1956         StackFrame *frame = SeqAt(ctx->stack, i);
1957         switch (frame->type)
1958         {
1959         case STACK_FRAME_TYPE_BODY:
1960             BufferAppendChar(path, '/');
1961             BufferAppend(path, frame->data.body.owner->name, CF_BUFSIZE);
1962             break;
1963 
1964         case STACK_FRAME_TYPE_BUNDLE:
1965             BufferAppendChar(path, '/');
1966             BufferAppend(path, frame->data.bundle.owner->ns, CF_BUFSIZE);
1967             BufferAppendChar(path, '/');
1968             BufferAppend(path, frame->data.bundle.owner->name, CF_BUFSIZE);
1969             break;
1970 
1971         case STACK_FRAME_TYPE_BUNDLE_SECTION:
1972             BufferAppendChar(path, '/');
1973             BufferAppend(path, frame->data.bundle_section.owner->promise_type, CF_BUFSIZE);
1974 
1975         case STACK_FRAME_TYPE_PROMISE:
1976             break;
1977 
1978         case STACK_FRAME_TYPE_PROMISE_ITERATION:
1979             BufferAppendChar(path, '/');
1980             BufferAppendChar(path, '\'');
1981             BufferAppendAbbreviatedStr(path, frame->data.promise_iteration.owner->promiser, CF_MAXFRAGMENT);
1982             BufferAppendChar(path, '\'');
1983             if (i == SeqLength(ctx->stack) - 1  &&
1984                 /* For some reason verify_packages.c is adding NULL iteration
1985                  * frames all over the place; TODO fix. */
1986                 frame->data.promise_iteration.iter_ctx != NULL)
1987             {
1988                 BufferAppendF(path, "[%zu]",
1989                               PromiseIteratorIndex(frame->data.promise_iteration.iter_ctx));
1990             }
1991             break;
1992 
1993             default:
1994                 ProgrammingError("Unhandled stack frame type");
1995         }
1996     }
1997 
1998     return BufferClose(path);
1999 }
2000 
EvalContextStackPromisees(const EvalContext * ctx)2001 StringSet *EvalContextStackPromisees(const EvalContext *ctx)
2002 {
2003     StringSet *promisees = StringSetNew();
2004 
2005     for (size_t i = 0; i < SeqLength(ctx->stack); i++)
2006     {
2007         StackFrame *frame = SeqAt(ctx->stack, i);
2008         if (frame->type != STACK_FRAME_TYPE_PROMISE_ITERATION)
2009         {
2010             continue;
2011         }
2012 
2013         Rval promisee = frame->data.promise_iteration.owner->promisee;
2014 
2015         switch (promisee.type)
2016         {
2017         case RVAL_TYPE_SCALAR:
2018             StringSetAdd(promisees, xstrdup(RvalScalarValue(promisee)));
2019             break;
2020 
2021         case RVAL_TYPE_LIST:
2022             {
2023                 for (const Rlist *rp = RvalRlistValue(promisee); rp; rp = rp->next)
2024                 {
2025                     if (rp->val.type == RVAL_TYPE_SCALAR)
2026                     {
2027                         StringSetAdd(promisees, xstrdup(RvalScalarValue(rp->val)));
2028                     }
2029                     else
2030                     {
2031                         assert(false && "Canary: promisee list contained non-scalar value");
2032                     }
2033                 }
2034             }
2035             break;
2036 
2037         case RVAL_TYPE_NOPROMISEE:
2038             break;
2039 
2040         default:
2041             assert(false && "Canary: promisee not scalar or list");
2042         }
2043     }
2044 
2045     return promisees;
2046 }
2047 
2048 /**
2049  * We cannot have double-scoped variables (e.g. "this.config.var1"), so if we
2050  * want to put a scoped variable into a special scope, we need to mangle the
2051  * name like this:
2052  *   "config.var1" -> "config___var1"
2053  */
MangleScopedVarNameIntoSpecialScopeName(const char * scope,const char * var_name)2054 static inline char *MangleScopedVarNameIntoSpecialScopeName(const char *scope, const char *var_name)
2055 {
2056     const size_t var_name_len = strlen(var_name);
2057 
2058     /* Replace '.' with NESTED_SCOPE_SEP */
2059     char *new_var_name = xmalloc(var_name_len + sizeof(NESTED_SCOPE_SEP));
2060     memcpy(new_var_name, var_name, var_name_len + 1 /* including '\0' */);
2061 
2062     /* Make sure we only replace the "scope." string, not all dots. */
2063     char *scope_with_dot = StringConcatenate(2, scope, ".");
2064     char *scope_with_underscores = StringConcatenate(2, scope, NESTED_SCOPE_SEP);
2065 
2066     NDEBUG_UNUSED ssize_t ret = StringReplace(new_var_name, var_name_len + sizeof(NESTED_SCOPE_SEP),
2067                                               scope_with_dot, scope_with_underscores);
2068     assert(ret == (var_name_len + sizeof(NESTED_SCOPE_SEP) - 2));
2069 
2070     free(scope_with_dot);
2071     free(scope_with_underscores);
2072 
2073     return new_var_name;
2074 }
2075 
2076 /*
2077  * Copies value, so you need to free your own copy afterwards.
2078  */
EvalContextVariablePutSpecial(EvalContext * ctx,SpecialScope scope,const char * lval,const void * value,DataType type,const char * tags)2079 bool EvalContextVariablePutSpecial(EvalContext *ctx, SpecialScope scope, const char *lval, const void *value, DataType type, const char *tags)
2080 {
2081     StringSet *tags_set = (NULL_OR_EMPTY(tags) ? NULL : StringSetFromString(tags, ','));
2082     bool ret = EvalContextVariablePutSpecialTagsSet(ctx, scope, lval, value, type, tags_set);
2083     if (!ret)
2084     {
2085         StringSetDestroy(tags_set);
2086     }
2087     return ret;
2088 }
2089 
2090 /**
2091  * Copies value, so you need to free your own copy afterwards, EXCEPT FOR THE
2092  * 'tags' SET which is taken over as-is IF THE VARIABLE IS SUCCESSFULLY ADDED.
2093  */
EvalContextVariablePutSpecialTagsSet(EvalContext * ctx,SpecialScope scope,const char * lval,const void * value,DataType type,StringSet * tags)2094 bool EvalContextVariablePutSpecialTagsSet(EvalContext *ctx, SpecialScope scope,
2095                                           const char *lval, const void *value,
2096                                           DataType type, StringSet *tags)
2097 {
2098     return EvalContextVariablePutSpecialTagsSetWithComment(ctx, scope, lval, value, type, tags, NULL);
2099 }
2100 
EvalContextVariablePutSpecialTagsSetWithComment(EvalContext * ctx,SpecialScope scope,const char * lval,const void * value,DataType type,StringSet * tags,const char * comment)2101 bool EvalContextVariablePutSpecialTagsSetWithComment(EvalContext *ctx, SpecialScope scope,
2102                                                      const char *lval, const void *value,
2103                                                      DataType type, StringSet *tags,
2104                                                      const char *comment)
2105 {
2106     char *new_lval = NULL;
2107     if (strchr(lval, '.') != NULL)
2108     {
2109         VarRef *ref = VarRefParse(lval);
2110         if (ref->scope != NULL)
2111         {
2112             new_lval = MangleScopedVarNameIntoSpecialScopeName(ref->scope, lval);
2113         }
2114         VarRefDestroy(ref);
2115     }
2116     if (strchr(lval, '['))
2117     {
2118         // dealing with (legacy) array reference in lval, must parse
2119         VarRef *ref = VarRefParseFromScope(new_lval ? new_lval : lval, SpecialScopeToString(scope));
2120         bool ret = EvalContextVariablePutTagsSetWithComment(ctx, ref, value, type, tags, comment);
2121         free(new_lval);
2122         VarRefDestroy(ref);
2123         return ret;
2124     }
2125     else
2126     {
2127         // plain lval, skip parsing
2128         const VarRef ref = VarRefConst(NULL, SpecialScopeToString(scope), new_lval ? new_lval : lval);
2129         bool ret = EvalContextVariablePutTagsSetWithComment(ctx, &ref, value, type, tags, comment);
2130         free(new_lval);
2131         return ret;
2132     }
2133 }
2134 
EvalContextVariableGetSpecial(const EvalContext * const ctx,const SpecialScope scope,const char * const varname,DataType * const type_out)2135 const void *EvalContextVariableGetSpecial(
2136     const EvalContext *const ctx,
2137     const SpecialScope scope,
2138     const char *const varname,
2139     DataType *const type_out)
2140 {
2141     VarRef *const ref = VarRefParseFromScope(
2142         varname, SpecialScopeToString(scope));
2143     const void *const result = EvalContextVariableGet(ctx, ref, type_out);
2144     VarRefDestroy(ref);
2145 
2146     return result;
2147 }
2148 
2149 /**
2150  * @note Only use this when you know the variable is a string
2151  * @see EvalContextVariableGetSpecial()
2152  */
EvalContextVariableGetSpecialString(const EvalContext * const ctx,const SpecialScope scope,const char * const varname)2153 const char *EvalContextVariableGetSpecialString(
2154     const EvalContext *const ctx,
2155     const SpecialScope scope,
2156     const char *const varname)
2157 {
2158     DataType type_out;
2159     const void *const result = EvalContextVariableGetSpecial(
2160         ctx, scope, varname, &type_out);
2161     assert(type_out == CF_DATA_TYPE_STRING); // Programming error if not string
2162     return (type_out == CF_DATA_TYPE_STRING) ? result : NULL;
2163 }
2164 
EvalContextVariableRemoveSpecial(const EvalContext * ctx,SpecialScope scope,const char * lval)2165 bool EvalContextVariableRemoveSpecial(const EvalContext *ctx, SpecialScope scope, const char *lval)
2166 {
2167     switch (scope)
2168     {
2169     case SPECIAL_SCOPE_SYS:
2170     case SPECIAL_SCOPE_MON:
2171     case SPECIAL_SCOPE_CONST:
2172     case SPECIAL_SCOPE_EDIT:
2173     case SPECIAL_SCOPE_BODY:
2174     case SPECIAL_SCOPE_THIS:
2175         {
2176             VarRef *ref = VarRefParseFromScope(lval, SpecialScopeToString(scope));
2177             bool ret = EvalContextVariableRemove(ctx, ref);
2178             VarRefDestroy(ref);
2179             return ret;
2180         }
2181 
2182     case SPECIAL_SCOPE_NONE:
2183         assert(false && "Attempted to remove none-special variable");
2184         return false;
2185 
2186     default:
2187         assert(false && "Unhandled case in switch");
2188         return false;
2189     }
2190 }
2191 
GetVariableTableForScope(const EvalContext * ctx,NDEBUG_UNUSED const char * ns,const char * scope)2192 static VariableTable *GetVariableTableForScope(const EvalContext *ctx,
2193                                                NDEBUG_UNUSED const char *ns, /* only used in assertions ... */
2194                                                const char *scope)
2195 {
2196     assert(ctx != NULL);
2197 
2198     switch (SpecialScopeFromString(scope))
2199     {
2200     case SPECIAL_SCOPE_DEF:
2201         /* 'def.' is not as special as the other scopes below. (CFE-3668) */
2202         return ctx->global_variables;
2203 
2204     case SPECIAL_SCOPE_SYS:
2205     case SPECIAL_SCOPE_MON:
2206     case SPECIAL_SCOPE_CONST:
2207         assert(!ns || strcmp("default", ns) == 0);
2208         return ctx->global_variables;
2209 
2210     case SPECIAL_SCOPE_MATCH:
2211         assert(!ns || strcmp("default", ns) == 0);
2212         return ctx->match_variables;
2213 
2214     case SPECIAL_SCOPE_EDIT:
2215         assert(!ns || strcmp("default", ns) == 0);
2216         {
2217             StackFrame *frame = LastStackFrameByType(ctx, STACK_FRAME_TYPE_BUNDLE);
2218             assert(frame);
2219             return frame->data.bundle.vars;
2220         }
2221 
2222     case SPECIAL_SCOPE_BODY:
2223         assert(!ns || strcmp("default", ns) == 0);
2224         {
2225             StackFrame *frame = LastStackFrameByType(ctx, STACK_FRAME_TYPE_BODY);
2226             return frame ? frame->data.body.vars : NULL;
2227         }
2228 
2229     // "this" variables can be in local or global variable table (when this is used for non-special
2230     // varables), so return local as VariableResolve will try global table anyway.
2231     case SPECIAL_SCOPE_THIS:
2232         {
2233             StackFrame *frame = LastStackFrameByType(ctx, STACK_FRAME_TYPE_PROMISE);
2234             return frame ? frame->data.promise.vars : NULL;
2235         }
2236 
2237     case SPECIAL_SCOPE_NONE:
2238         return ctx->global_variables;
2239 
2240     default:
2241         assert(false && "Unhandled case in switch");
2242         return NULL;
2243     }
2244 }
2245 
EvalContextVariableRemove(const EvalContext * ctx,const VarRef * ref)2246 bool EvalContextVariableRemove(const EvalContext *ctx, const VarRef *ref)
2247 {
2248     VariableTable *table = GetVariableTableForScope(ctx, ref->ns, ref->scope);
2249     return VariableTableRemove(table, ref);
2250 }
2251 
IsVariableSelfReferential(const VarRef * ref,const void * value,RvalType rval_type)2252 static bool IsVariableSelfReferential(const VarRef *ref, const void *value, RvalType rval_type)
2253 {
2254     switch (rval_type)
2255     {
2256     case RVAL_TYPE_SCALAR:
2257         if (StringContainsVar(value, ref->lval))
2258         {
2259             char *ref_str = VarRefToString(ref, true);
2260             Log(LOG_LEVEL_ERR, "The value of variable '%s' contains a reference to itself, '%s'", ref_str, (char *)value);
2261             free(ref_str);
2262             return true;
2263         }
2264         break;
2265 
2266     case RVAL_TYPE_LIST:
2267         for (const Rlist *rp = value; rp != NULL; rp = rp->next)
2268         {
2269             if (rp->val.type != RVAL_TYPE_SCALAR)
2270             {
2271                 continue;
2272             }
2273 
2274             if (StringContainsVar(RlistScalarValue(rp), ref->lval))
2275             {
2276                 char *ref_str = VarRefToString(ref, true);
2277                 Log(LOG_LEVEL_ERR, "An item in list variable '%s' contains a reference to itself", ref_str);
2278                 free(ref_str);
2279                 return true;
2280             }
2281         }
2282         break;
2283 
2284     case RVAL_TYPE_FNCALL:
2285     case RVAL_TYPE_CONTAINER:
2286     case RVAL_TYPE_NOPROMISEE:
2287         break;
2288     }
2289 
2290     return false;
2291 }
2292 
VarRefStackQualify(const EvalContext * ctx,VarRef * ref)2293 static void VarRefStackQualify(const EvalContext *ctx, VarRef *ref)
2294 {
2295     StackFrame *last_frame = LastStackFrame(ctx, 0);
2296     assert(last_frame);
2297 
2298     switch (last_frame->type)
2299     {
2300     case STACK_FRAME_TYPE_BODY:
2301         VarRefQualify(ref, NULL, SpecialScopeToString(SPECIAL_SCOPE_BODY));
2302         break;
2303 
2304     case STACK_FRAME_TYPE_BUNDLE_SECTION:
2305         {
2306             StackFrame *last_last_frame = LastStackFrame(ctx, 1);
2307             assert(last_last_frame);
2308             assert(last_last_frame->type == STACK_FRAME_TYPE_BUNDLE);
2309             VarRefQualify(ref,
2310                           last_last_frame->data.bundle.owner->ns,
2311                           last_last_frame->data.bundle.owner->name);
2312         }
2313         break;
2314 
2315     case STACK_FRAME_TYPE_BUNDLE:
2316         VarRefQualify(ref,
2317                       last_frame->data.bundle.owner->ns,
2318                       last_frame->data.bundle.owner->name);
2319         break;
2320 
2321     case STACK_FRAME_TYPE_PROMISE:
2322     case STACK_FRAME_TYPE_PROMISE_ITERATION:
2323         // Allow special "this" variables to work when used without "this"
2324         VarRefQualify(ref, NULL, SpecialScopeToString(SPECIAL_SCOPE_THIS));
2325         break;
2326 
2327     default:
2328         ProgrammingError("Unhandled stack frame type");
2329     }
2330 }
2331 
2332 /*
2333  * Copies value, so you need to free your own copy afterwards.
2334  */
EvalContextVariablePut(EvalContext * ctx,const VarRef * ref,const void * value,DataType type,const char * tags)2335 bool EvalContextVariablePut(EvalContext *ctx,
2336                             const VarRef *ref, const void *value,
2337                             DataType type, const char *tags)
2338 {
2339     StringSet *tags_set = (NULL_OR_EMPTY(tags) ? NULL : StringSetFromString(tags, ','));
2340     bool ret = EvalContextVariablePutTagsSet(ctx, ref, value, type, tags_set);
2341     if (!ret)
2342     {
2343         StringSetDestroy(tags_set);
2344     }
2345     return ret;
2346 }
2347 
2348 /**
2349  * Copies value, so you need to free your own copy afterwards, EXCEPT FOR THE
2350  * 'tags' SET which is taken over as-is IF THE VARIABLE IS SUCCESSFULLY ADDED.
2351  */
EvalContextVariablePutTagsSet(EvalContext * ctx,const VarRef * ref,const void * value,DataType type,StringSet * tags)2352 bool EvalContextVariablePutTagsSet(EvalContext *ctx,
2353                                    const VarRef *ref, const void *value,
2354                                    DataType type, StringSet *tags)
2355 {
2356     return EvalContextVariablePutTagsSetWithComment(ctx, ref, value, type, tags, NULL);
2357 }
2358 
EvalContextVariablePutTagsSetWithComment(EvalContext * ctx,const VarRef * ref,const void * value,DataType type,StringSet * tags,const char * comment)2359 bool EvalContextVariablePutTagsSetWithComment(EvalContext *ctx,
2360                                               const VarRef *ref, const void *value,
2361                                               DataType type, StringSet *tags,
2362                                               const char *comment)
2363 {
2364     assert(type != CF_DATA_TYPE_NONE);
2365     assert(ref);
2366     assert(ref->lval);
2367 
2368     /* The only possible way to get a NULL value is if it's an empty linked
2369      * list (Rlist usually). */
2370     assert(value != NULL || DataTypeIsIterable(type));
2371 
2372     if (strlen(ref->lval) > CF_MAXVARSIZE)
2373     {
2374         char *lval_str = VarRefToString(ref, true);
2375         Log(LOG_LEVEL_ERR, "Variable '%s'' cannot be added because "
2376             "its length exceeds the maximum length allowed ('%d' characters)",
2377             lval_str, CF_MAXVARSIZE);
2378         free(lval_str);
2379         return false;
2380     }
2381 
2382     if (strcmp(ref->scope, "body") != 0 &&
2383         IsVariableSelfReferential(ref, value, DataTypeToRvalType(type)))
2384     {
2385         return false;
2386     }
2387 
2388     Rval rval = (Rval) { (void *)value, DataTypeToRvalType(type) };
2389     VariableTable *table = GetVariableTableForScope(ctx, ref->ns, ref->scope);
2390     const Promise *pp = EvalContextStackCurrentPromise(ctx);
2391     VariableTablePut(table, ref, &rval, type, tags, SafeStringDuplicate(comment), pp ? pp->org_pp : pp);
2392     return true;
2393 }
2394 
2395 /**
2396  * Change ref for e.g. 'config.var1' to 'this.config___var1'
2397  *
2398  * @see MangleScopedVarNameIntoSpecialScopeName()
2399  */
MangledThisScopedRef(const VarRef * ref)2400 static inline VarRef *MangledThisScopedRef(const VarRef *ref)
2401 {
2402     VarRef *mangled_this_ref = VarRefCopy(ref);
2403     char *scope_underscores_lval = StringConcatenate(3, mangled_this_ref->scope,
2404                                                      NESTED_SCOPE_SEP,
2405                                                      mangled_this_ref->lval);
2406     free(mangled_this_ref->lval);
2407     mangled_this_ref->lval = scope_underscores_lval;
2408     free(mangled_this_ref->scope);
2409     mangled_this_ref->scope = xstrdup("this");
2410 
2411     return mangled_this_ref;
2412 }
2413 
VariableResolve2(const EvalContext * ctx,const VarRef * ref)2414 static Variable *VariableResolve2(const EvalContext *ctx, const VarRef *ref)
2415 {
2416     assert(ref != NULL);
2417 
2418     // Get the variable table associated to the scope
2419     VariableTable *table = GetVariableTableForScope(ctx, ref->ns, ref->scope);
2420 
2421     Variable *var;
2422     if (table)
2423     {
2424         var = VariableTableGet(table, ref);
2425         if (var)
2426         {
2427             return var;
2428         }
2429         else if (ref->num_indices > 0)
2430         {
2431             /* Iteration over slists creates special variables in the 'this.'
2432              * scope with the slist variable replaced by the individual
2433              * values. However, if a scoped variable is part of the variable
2434              * reference, e.g. 'config.data[$(list)]', the special iteration
2435              * variables use mangled names to avoid having two scopes
2436              * (e.g. 'this.config___data[list_item1]' instead of
2437              * 'this.config.data[list_item1]').
2438              *
2439              * If the ref we are looking for has indices and it has a scope, it
2440              * might be the case described above. Let's give it a try before
2441              * falling back to the indexless container lookup described below
2442              * (which will not have the list-iteration variables expanded). */
2443             if (ref->scope != NULL)
2444             {
2445                 VariableTable *this_table = GetVariableTableForScope(ctx, ref->ns,
2446                                                                      SpecialScopeToString(SPECIAL_SCOPE_THIS));
2447                 if (this_table != NULL)
2448                 {
2449                     VarRef *mangled_this_ref = MangledThisScopedRef(ref);
2450                     var = VariableTableGet(this_table, mangled_this_ref);
2451                     VarRefDestroy(mangled_this_ref);
2452                     if (var != NULL)
2453                     {
2454                         return var;
2455                     }
2456                 }
2457             }
2458 
2459             /* If the lookup with indices (the [idx1][idx2]... part of the
2460              * variable reference) fails, there might still be a container
2461              * variable where the indices actually refer to child objects inside
2462              * the container structure. */
2463             VarRef *base_ref = VarRefCopyIndexless(ref);
2464             var = VariableTableGet(table, base_ref);
2465             VarRefDestroy(base_ref);
2466 
2467             if (var && (VariableGetType(var) == CF_DATA_TYPE_CONTAINER))
2468             {
2469                 return var;
2470             }
2471         }
2472     }
2473 
2474     return NULL;
2475 }
2476 
2477 /*
2478  * Looks up a variable in the the context of the 'current scope'. This
2479  * basically means that an unqualified reference will be looked up in the
2480  * context of the top stack frame.
2481  *
2482  * Note that when evaluating a promise, this
2483  * will qualify a reference to 'this' scope and when evaluating a body, it
2484  * will qualify a reference to 'body' scope.
2485  */
VariableResolve(const EvalContext * ctx,const VarRef * ref)2486 static Variable *VariableResolve(const EvalContext *ctx, const VarRef *ref)
2487 {
2488     assert(ref->lval);
2489 
2490     /* We will make a first lookup that works in almost all cases: will look
2491      * for local or global variables, depending of the current scope. */
2492 
2493     Variable *ret_var = VariableResolve2(ctx, ref);
2494     if (ret_var != NULL)
2495     {
2496         return ret_var;
2497     }
2498 
2499     /* Try to qualify non-scoped vars to the scope:
2500        "this" for promises, "body" for bodies, current bundle for bundles. */
2501     VarRef *scoped_ref = NULL;
2502     if (!VarRefIsQualified(ref))
2503     {
2504         scoped_ref = VarRefCopy(ref);
2505         VarRefStackQualify(ctx, scoped_ref);
2506         ret_var = VariableResolve2(ctx, scoped_ref);
2507         if (ret_var != NULL)
2508         {
2509             VarRefDestroy(scoped_ref);
2510             return ret_var;
2511         }
2512         ref = scoped_ref;              /* continue with the scoped variable */
2513     }
2514 
2515     const Bundle *last_bundle = EvalContextStackCurrentBundle(ctx);
2516 
2517     /* If we are in a promise or a body, the variable might be coming from the
2518      * last bundle. So try a last lookup with "this" or "body" special scopes
2519      * replaced with the last bundle. */
2520 
2521     if ((SpecialScopeFromString(ref->scope) == SPECIAL_SCOPE_THIS  ||
2522          SpecialScopeFromString(ref->scope) == SPECIAL_SCOPE_BODY)
2523         &&  last_bundle != NULL)
2524     {
2525         VarRef *ref2 = VarRefCopy(ref);
2526         VarRefQualify(ref2, last_bundle->ns, last_bundle->name);
2527         ret_var = VariableResolve2(ctx, ref2);
2528 
2529         VarRefDestroy(scoped_ref);
2530         VarRefDestroy(ref2);
2531         return ret_var;
2532     }
2533     VarRefDestroy(scoped_ref);
2534 
2535     return NULL;
2536 }
2537 
2538 /**
2539  *
2540  * @NOTE NULL is a valid return value if #type_out is of list type and the
2541  *       list is empty. To check if the variable didn't resolve, check if
2542  *       #type_out was set to CF_DATA_TYPE_NONE.
2543  */
EvalContextVariableGet(const EvalContext * ctx,const VarRef * ref,DataType * type_out)2544 const void *EvalContextVariableGet(const EvalContext *ctx, const VarRef *ref, DataType *type_out)
2545 {
2546     Variable *var = VariableResolve(ctx, ref);
2547     if (var)
2548     {
2549         const VarRef *var_ref = VariableGetRef(var);
2550         DataType var_type = VariableGetType(var);
2551         Rval var_rval = VariableGetRval(var, true);
2552 
2553         if (var_ref->num_indices == 0    &&
2554                  ref->num_indices > 0     &&
2555             var_type == CF_DATA_TYPE_CONTAINER)
2556         {
2557             JsonElement *child = JsonSelect(RvalContainerValue(var_rval),
2558                                             ref->num_indices, ref->indices);
2559             if (child)
2560             {
2561                 if (type_out)
2562                 {
2563                     *type_out = CF_DATA_TYPE_CONTAINER;
2564                 }
2565                 return child;
2566             }
2567         }
2568         else
2569         {
2570             if (type_out)
2571             {
2572                 *type_out = var_type;
2573             }
2574             return var_rval.item;
2575         }
2576     }
2577 
2578     if (type_out)
2579     {
2580         *type_out = CF_DATA_TYPE_NONE;
2581     }
2582     return NULL;
2583 }
2584 
EvalContextVariablePromiseGet(const EvalContext * ctx,const VarRef * ref)2585 const Promise *EvalContextVariablePromiseGet(const EvalContext *ctx, const VarRef *ref)
2586 {
2587     Variable *var = VariableResolve(ctx, ref);
2588     return var ? VariableGetPromise(var) : NULL;
2589 }
2590 
EvalContextClassTags(const EvalContext * ctx,const char * ns,const char * name)2591 StringSet *EvalContextClassTags(const EvalContext *ctx, const char *ns, const char *name)
2592 {
2593     Class *cls = EvalContextClassGet(ctx, ns, name);
2594     if (!cls)
2595     {
2596         return NULL;
2597     }
2598 
2599     assert(cls->tags != NULL);
2600     return cls->tags;
2601 }
2602 
EvalContextVariableTags(const EvalContext * ctx,const VarRef * ref)2603 StringSet *EvalContextVariableTags(const EvalContext *ctx, const VarRef *ref)
2604 {
2605     Variable *var = VariableResolve(ctx, ref);
2606     if (!var)
2607     {
2608         return NULL;
2609     }
2610 
2611     StringSet *var_tags = VariableGetTags(var);
2612     return var_tags;
2613 }
2614 
EvalContextVariableClearMatch(EvalContext * ctx)2615 bool EvalContextVariableClearMatch(EvalContext *ctx)
2616 {
2617     return VariableTableClear(ctx->match_variables, NULL, NULL, NULL);
2618 }
2619 
EvalContextVariableTableIteratorNew(const EvalContext * ctx,const char * ns,const char * scope,const char * lval)2620 VariableTableIterator *EvalContextVariableTableIteratorNew(const EvalContext *ctx, const char *ns, const char *scope, const char *lval)
2621 {
2622     VariableTable *table = scope ? GetVariableTableForScope(ctx, ns, scope) : ctx->global_variables;
2623     return table ? VariableTableIteratorNew(table, ns, scope, lval) : NULL;
2624 }
2625 
2626 
EvalContextVariableTableFromRefIteratorNew(const EvalContext * ctx,const VarRef * ref)2627 VariableTableIterator *EvalContextVariableTableFromRefIteratorNew(const EvalContext *ctx, const VarRef *ref)
2628 {
2629     assert(ref);
2630     VariableTable *table = ref->scope ? GetVariableTableForScope(ctx, ref->ns, ref->scope) : ctx->global_variables;
2631     return table ? VariableTableIteratorNewFromVarRef(table, ref) : NULL;
2632 }
2633 
EvalContextVariableControlCommonGet(const EvalContext * ctx,CommonControl lval)2634 const void *EvalContextVariableControlCommonGet(const EvalContext *ctx, CommonControl lval)
2635 {
2636     assert(lval >= 0 && lval < COMMON_CONTROL_MAX);
2637 
2638     VarRef *ref = VarRefParseFromScope(CFG_CONTROLBODY[lval].lval, "control_common");
2639     const void *ret = EvalContextVariableGet(ctx, ref, NULL);
2640     VarRefDestroy(ref);
2641     return ret;
2642 }
2643 
IDRefQualify(const EvalContext * ctx,const char * id)2644 static ClassRef IDRefQualify(const EvalContext *ctx, const char *id)
2645 {
2646     // HACK: Because call reference names are equivalent to class names, we abuse ClassRef here
2647     ClassRef ref = ClassRefParse(id);
2648     if (!ClassRefIsQualified(ref))
2649     {
2650         const char *ns = EvalContextCurrentNamespace(ctx);
2651         if (ns)
2652         {
2653             ClassRefQualify(&ref, ns);
2654         }
2655         else
2656         {
2657             ClassRefQualify(&ref, NamespaceDefault());
2658         }
2659     }
2660 
2661     return ref;
2662 }
2663 
EvalContextResolveBundleExpression(const EvalContext * ctx,const Policy * policy,const char * callee_reference,const char * callee_type)2664 const Bundle *EvalContextResolveBundleExpression(const EvalContext *ctx, const Policy *policy,
2665                                                const char *callee_reference, const char *callee_type)
2666 {
2667     ClassRef ref = IDRefQualify(ctx, callee_reference);
2668 
2669     const Bundle *bp = NULL;
2670     for (size_t i = 0; i < SeqLength(policy->bundles); i++)
2671     {
2672         const Bundle *curr_bp = SeqAt(policy->bundles, i);
2673         if ((strcmp(curr_bp->type, callee_type) != 0) ||
2674             (strcmp(curr_bp->name, ref.name) != 0) ||
2675             !StringEqual(curr_bp->ns, ref.ns))
2676         {
2677             continue;
2678         }
2679 
2680         bp = curr_bp;
2681         break;
2682     }
2683 
2684     ClassRefDestroy(ref);
2685     return bp;
2686 }
2687 
EvalContextFindFirstMatchingBody(const Policy * policy,const char * type,const char * namespace,const char * name)2688 const Body *EvalContextFindFirstMatchingBody(const Policy *policy, const char *type,
2689                                              const char *namespace, const char *name)
2690 {
2691     for (size_t i = 0; i < SeqLength(policy->bodies); i++)
2692     {
2693         const Body *curr_bp = SeqAt(policy->bodies, i);
2694         if ((strcmp(curr_bp->type, type) == 0) &&
2695             (strcmp(curr_bp->name, name) == 0) &&
2696             StringEqual(curr_bp->ns, namespace))
2697         {
2698             return curr_bp;
2699         }
2700     }
2701 
2702     return NULL;
2703 }
2704 
EvalContextAppendBodyParentsAndArgs(const EvalContext * ctx,const Policy * policy,Seq * chain,const Body * bp,const char * callee_type,int depth)2705 void EvalContextAppendBodyParentsAndArgs(const EvalContext *ctx, const Policy *policy,
2706                                          Seq* chain, const Body *bp, const char *callee_type,
2707                                          int depth)
2708 {
2709     if (depth > 30) // sanity check
2710     {
2711         Log(LOG_LEVEL_ERR, "EvalContextAppendBodyParentsAndArgs: body inheritance chain depth %d in body %s is too much, aborting", depth, bp->name);
2712         DoCleanupAndExit(EXIT_FAILURE);
2713     }
2714 
2715     for (size_t k = 0; bp->conlist && k < SeqLength(bp->conlist); k++)
2716     {
2717         Constraint *scp = SeqAt(bp->conlist, k);
2718         if (strcmp("inherit_from", scp->lval) == 0)
2719         {
2720             char* call = NULL;
2721 
2722             if (RVAL_TYPE_SCALAR == scp->rval.type)
2723             {
2724                 call = RvalScalarValue(scp->rval);
2725             }
2726             else if (RVAL_TYPE_FNCALL == scp->rval.type)
2727             {
2728                 call = RvalFnCallValue(scp->rval)->name;
2729             }
2730 
2731             ClassRef parent_ref = IDRefQualify(ctx, call);
2732 
2733             // We don't do a more detailed check for circular
2734             // inheritance because the depth check above will catch it
2735             if (strcmp(parent_ref.name, bp->name) == 0)
2736             {
2737                 Log(LOG_LEVEL_ERR, "EvalContextAppendBodyParentsAndArgs: self body inheritance in %s->%s, aborting", bp->name, parent_ref.name);
2738                 DoCleanupAndExit(EXIT_FAILURE);
2739             }
2740 
2741             const Body *parent = EvalContextFindFirstMatchingBody(policy, callee_type, parent_ref.ns, parent_ref.name);
2742             if (parent)
2743             {
2744                 SeqAppend(chain, (void *)parent);
2745                 SeqAppend(chain, &(scp->rval));
2746                 EvalContextAppendBodyParentsAndArgs(ctx, policy, chain, parent, callee_type, depth+1);
2747             }
2748             ClassRefDestroy(parent_ref);
2749         }
2750     }
2751 }
2752 
EvalContextResolveBodyExpression(const EvalContext * ctx,const Policy * policy,const char * callee_reference,const char * callee_type)2753 Seq *EvalContextResolveBodyExpression(const EvalContext *ctx, const Policy *policy,
2754                                       const char *callee_reference, const char *callee_type)
2755 {
2756     ClassRef ref = IDRefQualify(ctx, callee_reference);
2757     Seq *bodies = NULL;
2758 
2759     const Body *bp = EvalContextFindFirstMatchingBody(policy, callee_type, ref.ns, ref.name);
2760     if (bp)
2761     {
2762         bodies = SeqNew(2, NULL);
2763         SeqAppend(bodies, (void *)bp);
2764         SeqAppend(bodies, (void *)NULL);
2765         EvalContextAppendBodyParentsAndArgs(ctx, policy, bodies, bp, callee_type, 1);
2766     }
2767 
2768     ClassRefDestroy(ref);
2769     return bodies;
2770 }
2771 
EvalContextPromiseLockCacheContains(const EvalContext * ctx,const char * key)2772 bool EvalContextPromiseLockCacheContains(const EvalContext *ctx, const char *key)
2773 {
2774     return StringSetContains(ctx->promise_lock_cache, key);
2775 }
2776 
EvalContextPromiseLockCachePut(EvalContext * ctx,const char * key)2777 void EvalContextPromiseLockCachePut(EvalContext *ctx, const char *key)
2778 {
2779     StringSetAdd(ctx->promise_lock_cache, xstrdup(key));
2780 }
2781 
EvalContextPromiseLockCacheRemove(EvalContext * ctx,const char * key)2782 void EvalContextPromiseLockCacheRemove(EvalContext *ctx, const char *key)
2783 {
2784     StringSetRemove(ctx->promise_lock_cache, key);
2785 }
2786 
EvalContextFunctionCacheGet(const EvalContext * ctx,const FnCall * fp ARG_UNUSED,const Rlist * args,Rval * rval_out)2787 bool EvalContextFunctionCacheGet(const EvalContext *ctx,
2788                                  const FnCall *fp ARG_UNUSED,
2789                                  const Rlist *args, Rval *rval_out)
2790 {
2791     if (!(ctx->eval_options & EVAL_OPTION_CACHE_SYSTEM_FUNCTIONS))
2792     {
2793         return false;
2794     }
2795 
2796     Rval *rval = FuncCacheMapGet(ctx->function_cache, args);
2797     if (rval)
2798     {
2799         if (rval_out)
2800         {
2801             *rval_out = *rval;
2802         }
2803         return true;
2804     }
2805     else
2806     {
2807         return false;
2808     }
2809 }
2810 
EvalContextFunctionCachePut(EvalContext * ctx,const FnCall * fp ARG_UNUSED,const Rlist * args,const Rval * rval)2811 void EvalContextFunctionCachePut(EvalContext *ctx,
2812                                  const FnCall *fp ARG_UNUSED,
2813                                  const Rlist *args, const Rval *rval)
2814 {
2815     if (!(ctx->eval_options & EVAL_OPTION_CACHE_SYSTEM_FUNCTIONS))
2816     {
2817         return;
2818     }
2819 
2820     Rval *rval_copy = xmalloc(sizeof(Rval));
2821     *rval_copy = RvalCopy(*rval);
2822     FuncCacheMapInsert(ctx->function_cache, RlistCopy(args), rval_copy);
2823 }
2824 
2825 /* cfPS and associated machinery */
2826 
2827 
2828 
2829 /*
2830  * Internal functions temporarily used from logging implementation
2831  */
2832 
2833 static const char *const NO_STATUS_TYPES[] =
2834     { "vars", "classes", "insert_lines", "delete_lines", "replace_patterns", "field_edits", NULL };
2835 static const char *const NO_LOG_TYPES[] =
2836     { "vars", "classes", "insert_lines", "delete_lines", "replace_patterns", "field_edits", NULL };
2837 
2838 /*
2839  * Vars, classes and similar promises which do not affect the system itself (but
2840  * just support evalution) do not need to be counted as repaired/failed, as they
2841  * may change every iteration and introduce lot of churn in reports without
2842  * giving any value.
2843  */
IsPromiseValuableForStatus(const Promise * pp)2844 static bool IsPromiseValuableForStatus(const Promise *pp)
2845 {
2846     return pp && (PromiseGetPromiseType(pp) != NULL) && (!IsStrIn(PromiseGetPromiseType(pp), NO_STATUS_TYPES));
2847 }
2848 
2849 /*
2850  * Vars, classes and subordinate promises (like edit_line) do not need to be
2851  * logged, as they exist to support other promises.
2852  */
2853 
IsPromiseValuableForLogging(const Promise * pp)2854 static bool IsPromiseValuableForLogging(const Promise *pp)
2855 {
2856     return pp && (PromiseGetPromiseType(pp) != NULL) && (!IsStrIn(PromiseGetPromiseType(pp), NO_LOG_TYPES));
2857 }
2858 
AddAllClasses(EvalContext * ctx,const Rlist * list,unsigned int persistence_ttl,PersistentClassPolicy policy,ContextScope context_scope)2859 static void AddAllClasses(EvalContext *ctx, const Rlist *list, unsigned int persistence_ttl,
2860                           PersistentClassPolicy policy, ContextScope context_scope)
2861 {
2862     for (const Rlist *rp = list; rp != NULL; rp = rp->next)
2863     {
2864         char *classname = xstrdup(RlistScalarValue(rp));
2865         if (strcmp(classname, "a_class_global_from_command") == 0 || strcmp(classname, "xxx:a_class_global_from_command") == 0)
2866         {
2867             Log(LOG_LEVEL_ERR, "Hit '%s'", classname);
2868         }
2869 
2870         CanonifyNameInPlace(classname);
2871 
2872         if (EvalContextHeapContainsHard(ctx, classname))
2873         {
2874             Log(LOG_LEVEL_ERR, "You cannot use reserved hard class '%s' as post-condition class", classname);
2875             // TODO: ok.. but should we take any action? continue; maybe?
2876         }
2877 
2878         if (persistence_ttl > 0)
2879         {
2880             if (context_scope != CONTEXT_SCOPE_NAMESPACE)
2881             {
2882                 Log(LOG_LEVEL_INFO, "Automatically promoting context scope for '%s' to namespace visibility, due to persistence", classname);
2883             }
2884 
2885             Log(LOG_LEVEL_VERBOSE, "C:    + persistent outcome class '%s'", classname);
2886             EvalContextHeapPersistentSave(ctx, classname, persistence_ttl, policy, "");
2887             EvalContextClassPutSoft(ctx, classname, CONTEXT_SCOPE_NAMESPACE, "");
2888         }
2889         else
2890         {
2891             Log(LOG_LEVEL_VERBOSE, "C:    + promise outcome class '%s'", classname);
2892 
2893             switch (context_scope)
2894             {
2895             case CONTEXT_SCOPE_BUNDLE:
2896                 EvalContextStackFrameAddSoft(ctx, classname, "");
2897                 break;
2898 
2899             case CONTEXT_SCOPE_NONE:
2900             case CONTEXT_SCOPE_NAMESPACE:
2901                 EvalContextClassPutSoft(ctx, classname, CONTEXT_SCOPE_NAMESPACE, "");
2902                 break;
2903 
2904             default:
2905                 ProgrammingError("AddAllClasses: Unexpected context_scope %d!",
2906                                  context_scope);
2907             }
2908         }
2909         free(classname);
2910     }
2911 }
2912 
DeleteAllClasses(EvalContext * ctx,const Rlist * list)2913 static void DeleteAllClasses(EvalContext *ctx, const Rlist *list)
2914 {
2915     for (const Rlist *rp = list; rp != NULL; rp = rp->next)
2916     {
2917         if (CheckParseContext(RlistScalarValue(rp), CF_IDRANGE) != SYNTAX_TYPE_MATCH_OK)
2918         {
2919             return; // TODO: interesting course of action, but why is the check there in the first place?
2920         }
2921 
2922         if (EvalContextHeapContainsHard(ctx, RlistScalarValue(rp)))
2923         {
2924             Log(LOG_LEVEL_ERR, "You cannot cancel a reserved hard class '%s' in post-condition classes",
2925                   RlistScalarValue(rp));
2926         }
2927 
2928         const char *string = RlistScalarValue(rp);
2929 
2930         Log(LOG_LEVEL_VERBOSE, "Cancelling class '%s'", string);
2931 
2932         EvalContextHeapPersistentRemove(string);
2933 
2934         {
2935             ClassRef ref = ClassRefParse(CanonifyName(string));
2936             EvalContextClassRemove(ctx, ref.ns, ref.name);
2937             ClassRefDestroy(ref);
2938         }
2939         EvalContextStackFrameRemoveSoft(ctx, CanonifyName(string));
2940     }
2941 }
2942 
ENTERPRISE_VOID_FUNC_2ARG_DEFINE_STUB(void,TrackTotalCompliance,ARG_UNUSED PromiseResult,status,ARG_UNUSED const Promise *,pp)2943 ENTERPRISE_VOID_FUNC_2ARG_DEFINE_STUB(void, TrackTotalCompliance, ARG_UNUSED PromiseResult, status, ARG_UNUSED const Promise *, pp)
2944 {
2945 }
2946 
SetPromiseOutcomeClasses(EvalContext * ctx,PromiseResult status,const DefineClasses * dc)2947 void SetPromiseOutcomeClasses(EvalContext *ctx, PromiseResult status, const DefineClasses *dc)
2948 {
2949     Rlist *add_classes = NULL;
2950     Rlist *del_classes = NULL;
2951 
2952     switch (status)
2953     {
2954     case PROMISE_RESULT_CHANGE:
2955         add_classes = dc->change;
2956         del_classes = dc->del_change;
2957         break;
2958 
2959     case PROMISE_RESULT_TIMEOUT:
2960         add_classes = dc->timeout;
2961         del_classes = dc->del_notkept;
2962         break;
2963 
2964     case PROMISE_RESULT_WARN:
2965     case PROMISE_RESULT_FAIL:
2966     case PROMISE_RESULT_INTERRUPTED:
2967         add_classes = dc->failure;
2968         del_classes = dc->del_notkept;
2969         break;
2970 
2971     case PROMISE_RESULT_DENIED:
2972         add_classes = dc->denied;
2973         del_classes = dc->del_notkept;
2974         break;
2975 
2976     case PROMISE_RESULT_NOOP:
2977         add_classes = dc->kept;
2978         del_classes = dc->del_kept;
2979         break;
2980 
2981     default:
2982         ProgrammingError("Unexpected status '%c' has been passed to SetPromiseOutcomeClasses", status);
2983     }
2984 
2985     AddAllClasses(ctx, add_classes, dc->persist, dc->timer, dc->scope);
2986     DeleteAllClasses(ctx, del_classes);
2987 }
2988 
SummarizeTransaction(EvalContext * ctx,const TransactionContext * tc,const char * logname)2989 static void SummarizeTransaction(EvalContext *ctx, const TransactionContext *tc, const char *logname)
2990 {
2991     if (logname && (tc->log_string))
2992     {
2993         Buffer *buffer = BufferNew();
2994         ExpandScalar(ctx, NULL, NULL, tc->log_string, buffer);
2995 
2996         if (strcmp(logname, "udp_syslog") == 0)
2997         {
2998             RemoteSysLog(tc->log_priority, BufferData(buffer));
2999         }
3000         else if (strcmp(logname, "stdout") == 0)
3001         {
3002             Log(LOG_LEVEL_INFO, "L: %s", BufferData(buffer));
3003         }
3004         else
3005         {
3006             struct stat dsb;
3007 
3008             // Does the file exist already?
3009             if (lstat(logname, &dsb) == -1)
3010             {
3011                 mode_t filemode = 0600;     /* Mode for log file creation */
3012                 int fd = creat(logname, filemode);
3013                 if (fd >= 0)
3014                 {
3015                     Log(LOG_LEVEL_VERBOSE,
3016                         "Created log file '%s' with requested permissions %jo",
3017                         logname, (intmax_t) filemode);
3018                     close(fd);
3019                 }
3020             }
3021 
3022             FILE *fout = safe_fopen(logname, "a");
3023 
3024             if (fout == NULL)
3025             {
3026                 Log(LOG_LEVEL_ERR, "Unable to open private log '%s'", logname);
3027                 return;
3028             }
3029 
3030             Log(LOG_LEVEL_VERBOSE, "Logging string '%s' to '%s'", BufferData(buffer), logname);
3031             fprintf(fout, "%s\n", BufferData(buffer));
3032 
3033             fclose(fout);
3034         }
3035 
3036         BufferDestroy(buffer);
3037         // FIXME: This was overwriting a local copy, with no side effects.
3038         // The intention was clearly to skip this function if called
3039         // repeatedly. Try to introduce this change:
3040         // tc.log_string = NULL;     /* To avoid repetition */
3041     }
3042 }
3043 
DoSummarizeTransaction(EvalContext * ctx,PromiseResult status,const Promise * pp,const TransactionContext * tc)3044 static void DoSummarizeTransaction(EvalContext *ctx, PromiseResult status, const Promise *pp, const TransactionContext *tc)
3045 {
3046     if (!IsPromiseValuableForLogging(pp))
3047     {
3048         return;
3049     }
3050 
3051     char *log_name = NULL;
3052 
3053     switch (status)
3054     {
3055     case PROMISE_RESULT_CHANGE:
3056         log_name = tc->log_repaired;
3057         break;
3058 
3059     case PROMISE_RESULT_WARN:
3060         /* FIXME: nothing? */
3061         return;
3062 
3063     case PROMISE_RESULT_TIMEOUT:
3064     case PROMISE_RESULT_FAIL:
3065     case PROMISE_RESULT_DENIED:
3066     case PROMISE_RESULT_INTERRUPTED:
3067         log_name = tc->log_failed;
3068         break;
3069 
3070     case PROMISE_RESULT_NOOP:
3071         log_name = tc->log_kept;
3072         break;
3073 
3074     default:
3075         ProgrammingError("Unexpected promise result status: %d", status);
3076     }
3077 
3078     SummarizeTransaction(ctx, tc, log_name);
3079 }
3080 
NotifyDependantPromises(EvalContext * ctx,const Promise * pp,PromiseResult result)3081 void NotifyDependantPromises(EvalContext *ctx, const Promise *pp, PromiseResult result)
3082 {
3083     switch (result)
3084     {
3085     case PROMISE_RESULT_CHANGE:
3086     case PROMISE_RESULT_NOOP:
3087         {
3088             const char *handle = PromiseGetHandle(pp);
3089             if (handle)
3090             {
3091                 StringSetAdd(ctx->dependency_handles, xstrdup(handle));
3092             }
3093         }
3094         break;
3095 
3096     default:
3097         /* This promise is not yet done, don't mark it is as such */
3098         break;
3099     }
3100 }
3101 
ClassAuditLog(EvalContext * ctx,const Promise * pp,const Attributes * attr,PromiseResult status)3102 void ClassAuditLog(EvalContext *ctx, const Promise *pp, const Attributes *attr, PromiseResult status)
3103 {
3104     assert(attr != NULL);
3105     if (IsPromiseValuableForStatus(pp))
3106     {
3107         TrackTotalCompliance(status, pp);
3108         UpdatePromiseCounters(status);
3109     }
3110 
3111     SetPromiseOutcomeClasses(ctx, status, &(attr->classes));
3112     DoSummarizeTransaction(ctx, status, pp, &(attr->transaction));
3113 }
3114 
LogPromiseContext(const EvalContext * ctx,const Promise * pp)3115 static void LogPromiseContext(const EvalContext *ctx, const Promise *pp)
3116 {
3117     if (!WouldLog(LOG_LEVEL_VERBOSE))
3118     {
3119         return;
3120     }
3121 
3122     Writer *w = StringWriter();
3123     WriterWrite(w, "Additional promise info:");
3124     if (PromiseGetHandle(pp))
3125     {
3126         WriterWriteF(w, " handle '%s'", PromiseGetHandle(pp));
3127     }
3128 
3129     {
3130         const char *version = EvalContextVariableControlCommonGet(ctx, COMMON_CONTROL_VERSION);
3131         if (version)
3132         {
3133             WriterWriteF(w, " version '%s'", version);
3134         }
3135     }
3136 
3137     if (PromiseGetBundle(pp)->source_path)
3138     {
3139         WriterWriteF(w, " source path '%s' at line %zu", PromiseGetBundle(pp)->source_path, pp->offset.line);
3140     }
3141 
3142     switch (pp->promisee.type)
3143     {
3144     case RVAL_TYPE_SCALAR:
3145         WriterWriteF(w, " promisee '%s'", RvalScalarValue(pp->promisee));
3146         break;
3147 
3148     case RVAL_TYPE_LIST:
3149         WriterWrite(w, " promisee ");
3150         RlistWrite(w, pp->promisee.item);
3151         break;
3152     default:
3153         break;
3154     }
3155 
3156     if (pp->comment)
3157     {
3158         WriterWriteF(w, " comment '%s'", pp->comment);
3159     }
3160 
3161     Log(LOG_LEVEL_VERBOSE, "%s", StringWriterData(w));
3162     WriterClose(w);
3163 }
3164 
cfPS(EvalContext * ctx,LogLevel level,PromiseResult status,const Promise * pp,const Attributes * attr,const char * fmt,...)3165 void cfPS(EvalContext *ctx, LogLevel level, PromiseResult status, const Promise *pp, const Attributes *attr, const char *fmt, ...)
3166 {
3167     assert(pp != NULL);
3168     assert(attr != NULL);
3169 
3170     /* Either logging something (based on 'fmt') or logging nothing. */
3171     assert(!NULL_OR_EMPTY(fmt) || (level == LOG_LEVEL_NOTHING));
3172 
3173     if (!NULL_OR_EMPTY(fmt))
3174     {
3175         if (level >= LOG_LEVEL_VERBOSE)
3176         {
3177             LogPromiseContext(ctx, pp);
3178         }
3179 
3180         va_list ap;
3181         va_start(ap, fmt);
3182         VLog(level, fmt, ap);
3183         va_end(ap);
3184     }
3185 
3186     /* Now complete the exits status classes and auditing */
3187 
3188     if (status != PROMISE_RESULT_SKIPPED)
3189     {
3190         ClassAuditLog(ctx, pp, attr, status);
3191     }
3192 }
3193 
RecordChange(EvalContext * ctx,const Promise * pp,const Attributes * attr,const char * fmt,...)3194 void RecordChange(EvalContext *ctx, const Promise *pp, const Attributes *attr, const char *fmt, ...)
3195 {
3196     assert(ctx != NULL);
3197     assert(pp != NULL);
3198     assert(attr != NULL);
3199 
3200     LogPromiseContext(ctx, pp);
3201 
3202     va_list ap;
3203     va_start(ap, fmt);
3204     VLog(LOG_LEVEL_INFO, fmt, ap);
3205     va_end(ap);
3206 
3207     SetPromiseOutcomeClasses(ctx, PROMISE_RESULT_CHANGE, &(attr->classes));
3208 }
3209 
RecordNoChange(EvalContext * ctx,const Promise * pp,const Attributes * attr,const char * fmt,...)3210 void RecordNoChange(EvalContext *ctx, const Promise *pp, const Attributes *attr, const char *fmt, ...)
3211 {
3212     assert(ctx != NULL);
3213     assert(pp != NULL);
3214     assert(attr != NULL);
3215 
3216     LogPromiseContext(ctx, pp);
3217 
3218     va_list ap;
3219     va_start(ap, fmt);
3220     VLog(LOG_LEVEL_VERBOSE, fmt, ap);
3221     va_end(ap);
3222 
3223     SetPromiseOutcomeClasses(ctx, PROMISE_RESULT_NOOP, &(attr->classes));
3224 }
3225 
RecordFailure(EvalContext * ctx,const Promise * pp,const Attributes * attr,const char * fmt,...)3226 void RecordFailure(EvalContext *ctx, const Promise *pp, const Attributes *attr, const char *fmt, ...)
3227 {
3228     assert(ctx != NULL);
3229     assert(pp != NULL);
3230     assert(attr != NULL);
3231 
3232     LogPromiseContext(ctx, pp);
3233 
3234     va_list ap;
3235     va_start(ap, fmt);
3236     VLog(LOG_LEVEL_ERR, fmt, ap);
3237     va_end(ap);
3238 
3239     SetPromiseOutcomeClasses(ctx, PROMISE_RESULT_FAIL, &(attr->classes));
3240 }
3241 
RecordWarning(EvalContext * ctx,const Promise * pp,const Attributes * attr,const char * fmt,...)3242 void RecordWarning(EvalContext *ctx, const Promise *pp, const Attributes *attr, const char *fmt, ...)
3243 {
3244     assert(ctx != NULL);
3245     assert(pp != NULL);
3246     assert(attr != NULL);
3247 
3248     LogPromiseContext(ctx, pp);
3249 
3250     va_list ap;
3251     va_start(ap, fmt);
3252     VLog(LOG_LEVEL_WARNING, fmt, ap);
3253     va_end(ap);
3254 
3255     SetPromiseOutcomeClasses(ctx, PROMISE_RESULT_WARN, &(attr->classes));
3256 }
3257 
RecordDenial(EvalContext * ctx,const Promise * pp,const Attributes * attr,const char * fmt,...)3258 void RecordDenial(EvalContext *ctx, const Promise *pp, const Attributes *attr, const char *fmt, ...)
3259 {
3260     assert(ctx != NULL);
3261     assert(pp != NULL);
3262     assert(attr != NULL);
3263 
3264     LogPromiseContext(ctx, pp);
3265 
3266     va_list ap;
3267     va_start(ap, fmt);
3268     VLog(LOG_LEVEL_ERR, fmt, ap);
3269     va_end(ap);
3270 
3271     SetPromiseOutcomeClasses(ctx, PROMISE_RESULT_DENIED, &(attr->classes));
3272 }
3273 
RecordInterruption(EvalContext * ctx,const Promise * pp,const Attributes * attr,const char * fmt,...)3274 void RecordInterruption(EvalContext *ctx, const Promise *pp, const Attributes *attr, const char *fmt, ...)
3275 {
3276     assert(ctx != NULL);
3277     assert(pp != NULL);
3278     assert(attr != NULL);
3279 
3280     LogPromiseContext(ctx, pp);
3281 
3282     va_list ap;
3283     va_start(ap, fmt);
3284     VLog(LOG_LEVEL_ERR, fmt, ap);
3285     va_end(ap);
3286 
3287     SetPromiseOutcomeClasses(ctx, PROMISE_RESULT_INTERRUPTED, &(attr->classes));
3288 }
3289 
MakingChanges(EvalContext * ctx,const Promise * pp,const Attributes * attr,PromiseResult * result,const char * change_desc_fmt,...)3290 bool MakingChanges(EvalContext *ctx, const Promise *pp, const Attributes *attr,
3291                    PromiseResult *result, const char *change_desc_fmt, ...)
3292 {
3293     assert(attr != NULL);
3294 
3295     if ((EVAL_MODE != EVAL_MODE_DRY_RUN) && (attr->transaction.action != cfa_warn))
3296     {
3297         return true;
3298     }
3299     /* else */
3300     char *fmt = NULL;
3301     if (attr->transaction.action == cfa_warn)
3302     {
3303         xasprintf(&fmt, "Should %s, but only warning promised", change_desc_fmt);
3304     }
3305     else
3306     {
3307         xasprintf(&fmt, "Should %s", change_desc_fmt);
3308     }
3309 
3310     LogPromiseContext(ctx, pp);
3311 
3312     va_list ap;
3313     va_start(ap, change_desc_fmt);
3314     VLog(LOG_LEVEL_WARNING, fmt, ap);
3315     va_end(ap);
3316 
3317     free(fmt);
3318 
3319     SetPromiseOutcomeClasses(ctx, PROMISE_RESULT_WARN, &(attr->classes));
3320 
3321     if (result != NULL)
3322     {
3323         *result = PROMISE_RESULT_WARN;
3324     }
3325 
3326     return false;
3327 }
3328 
MakingInternalChanges(EvalContext * ctx,const Promise * pp,const Attributes * attr,PromiseResult * result,const char * change_desc_fmt,...)3329 bool MakingInternalChanges(EvalContext *ctx, const Promise *pp, const Attributes *attr,
3330                            PromiseResult *result, const char *change_desc_fmt, ...)
3331 {
3332     assert(attr != NULL);
3333 
3334     if ((EVAL_MODE == EVAL_MODE_NORMAL) && (attr->transaction.action != cfa_warn))
3335     {
3336         return true;
3337     }
3338     /* else */
3339     char *fmt = NULL;
3340     if (attr->transaction.action == cfa_warn)
3341     {
3342         xasprintf(&fmt, "Should %s, but only warning promised", change_desc_fmt);
3343     }
3344     else
3345     {
3346         xasprintf(&fmt, "Should %s", change_desc_fmt);
3347     }
3348 
3349     LogPromiseContext(ctx, pp);
3350 
3351     va_list ap;
3352     va_start(ap, change_desc_fmt);
3353     VLog(LOG_LEVEL_WARNING, fmt, ap);
3354     va_end(ap);
3355 
3356     free(fmt);
3357 
3358     SetPromiseOutcomeClasses(ctx, PROMISE_RESULT_WARN, &(attr->classes));
3359 
3360     if (result != NULL)
3361     {
3362         *result = PROMISE_RESULT_WARN;
3363     }
3364 
3365     return false;
3366 }
3367 
SetChecksumUpdatesDefault(EvalContext * ctx,bool enabled)3368 void SetChecksumUpdatesDefault(EvalContext *ctx, bool enabled)
3369 {
3370     ctx->checksum_updates_default = enabled;
3371 }
3372 
GetChecksumUpdatesDefault(const EvalContext * ctx)3373 bool GetChecksumUpdatesDefault(const EvalContext *ctx)
3374 {
3375     return ctx->checksum_updates_default;
3376 }
3377 
EvalContextAddIpAddress(EvalContext * ctx,const char * ip_address,const char * iface)3378 void EvalContextAddIpAddress(EvalContext *ctx, const char *ip_address, const char *iface)
3379 {
3380     AppendItem(&ctx->ip_addresses, ip_address,
3381                (iface == NULL) ? "" : iface);
3382 }
3383 
EvalContextDeleteIpAddresses(EvalContext * ctx)3384 void EvalContextDeleteIpAddresses(EvalContext *ctx)
3385 {
3386     DeleteItemList(ctx->ip_addresses);
3387     ctx->ip_addresses = NULL;
3388 }
3389 
EvalContextGetIpAddresses(const EvalContext * ctx)3390 Item *EvalContextGetIpAddresses(const EvalContext *ctx)
3391 {
3392     return ctx->ip_addresses;
3393 }
3394 
EvalContextSetEvalOption(EvalContext * ctx,EvalContextOption option,bool value)3395 void EvalContextSetEvalOption(EvalContext *ctx, EvalContextOption option, bool value)
3396 {
3397     if (value)
3398     {
3399         ctx->eval_options |= option;
3400     }
3401     else
3402     {
3403         ctx->eval_options &= ~option;
3404     }
3405 }
3406 
EvalContextGetEvalOption(EvalContext * ctx,EvalContextOption option)3407 bool EvalContextGetEvalOption(EvalContext *ctx, EvalContextOption option)
3408 {
3409     return ((ctx->eval_options & option) != 0);
3410 }
3411 
EvalContextSetLaunchDirectory(EvalContext * ctx,const char * path)3412 void EvalContextSetLaunchDirectory(EvalContext *ctx, const char *path)
3413 {
3414     free(ctx->launch_directory);
3415     ctx->launch_directory = xstrdup(path);
3416 }
3417 
EvalContextSetEntryPoint(EvalContext * const ctx,const char * const entry_point)3418 void EvalContextSetEntryPoint(
3419     EvalContext *const ctx, const char *const entry_point)
3420 {
3421     assert(ctx != NULL);
3422     free(ctx->entry_point);
3423     ctx->entry_point = SafeStringDuplicate(entry_point);
3424 }
3425 
EvalContextGetEntryPoint(EvalContext * const ctx)3426 const char *EvalContextGetEntryPoint(EvalContext *const ctx)
3427 {
3428     assert(ctx != NULL);
3429     return ctx->entry_point;
3430 }
3431 
EvalContextSetIgnoreLocks(EvalContext * ctx,bool ignore)3432 void EvalContextSetIgnoreLocks(EvalContext *ctx, bool ignore)
3433 {
3434     ctx->ignore_locks = ignore;
3435 }
3436 
EvalContextIsIgnoringLocks(const EvalContext * ctx)3437 bool EvalContextIsIgnoringLocks(const EvalContext *ctx)
3438 {
3439     return ctx->ignore_locks;
3440 }
3441 
ClassesMatching(const EvalContext * ctx,ClassTableIterator * iter,const char * regex,const Rlist * tags,bool first_only)3442 StringSet *ClassesMatching(const EvalContext *ctx, ClassTableIterator *iter, const char* regex, const Rlist *tags, bool first_only)
3443 {
3444     StringSet *matching = StringSetNew();
3445 
3446     pcre *rx = CompileRegex(regex);
3447 
3448     Class *cls;
3449     while ((cls = ClassTableIteratorNext(iter)))
3450     {
3451         char *expr = ClassRefToString(cls->ns, cls->name);
3452 
3453         /* FIXME: review this strcmp. Moved out from StringMatch */
3454         if (!strcmp(regex, expr) ||
3455             (rx && StringMatchFullWithPrecompiledRegex(rx, expr)))
3456         {
3457             bool pass = false;
3458             StringSet *tagset = EvalContextClassTags(ctx, cls->ns, cls->name);
3459 
3460             if (tags)
3461             {
3462                 for (const Rlist *arg = tags; arg; arg = arg->next)
3463                 {
3464                     const char *tag_regex = RlistScalarValue(arg);
3465                     const char *element;
3466                     StringSetIterator it = StringSetIteratorInit(tagset);
3467                     while ((element = StringSetIteratorNext(&it)))
3468                     {
3469                         /* FIXME: review this strcmp. Moved out from StringMatch */
3470                         if (strcmp(tag_regex, element) == 0 ||
3471                             StringMatchFull(tag_regex, element))
3472                         {
3473                             pass = true;
3474                             break;
3475                         }
3476                     }
3477                 }
3478             }
3479             else                        // without any tags queried, accept class
3480             {
3481                 pass = true;
3482             }
3483 
3484             if (pass)
3485             {
3486                 StringSetAdd(matching, expr);
3487             }
3488             else
3489             {
3490                 free(expr);
3491             }
3492         }
3493         else
3494         {
3495             free(expr);
3496         }
3497 
3498         if (first_only && StringSetSize(matching) > 0)
3499         {
3500             break;
3501         }
3502     }
3503 
3504     if (rx)
3505     {
3506         pcre_free(rx);
3507     }
3508 
3509     return matching;
3510 }
3511 
JsonExpandElement(EvalContext * ctx,const JsonElement * source)3512 JsonElement* JsonExpandElement(EvalContext *ctx, const JsonElement *source)
3513 {
3514     if (JsonGetElementType(source) == JSON_ELEMENT_TYPE_PRIMITIVE)
3515     {
3516         Buffer *expbuf;
3517         JsonElement *expanded_json;
3518 
3519         if (JsonGetPrimitiveType(source) == JSON_PRIMITIVE_TYPE_STRING)
3520         {
3521             expbuf = BufferNew();
3522             ExpandScalar(ctx, NULL, "this", JsonPrimitiveGetAsString(source), expbuf);
3523             expanded_json = JsonStringCreate(BufferData(expbuf));
3524             BufferDestroy(expbuf);
3525             return expanded_json;
3526         }
3527         else
3528         {
3529             return JsonCopy(source);
3530         }
3531     }
3532     else if (JsonGetElementType(source) == JSON_ELEMENT_TYPE_CONTAINER)
3533     {
3534         if (JsonGetContainerType(source) == JSON_CONTAINER_TYPE_OBJECT)
3535         {
3536             JsonElement *dest = JsonObjectCreate(JsonLength(source));
3537             JsonIterator iter = JsonIteratorInit(source);
3538             const char *key;
3539             while ((key = JsonIteratorNextKey(&iter)))
3540             {
3541                 Buffer *expbuf = BufferNew();
3542                 ExpandScalar(ctx, NULL, "this", key, expbuf);
3543                 JsonObjectAppendElement(dest, BufferData(expbuf), JsonExpandElement(ctx, JsonObjectGet(source, key)));
3544                 BufferDestroy(expbuf);
3545             }
3546 
3547             return dest;
3548         }
3549         else
3550         {
3551             JsonElement *dest = JsonArrayCreate(JsonLength(source));
3552             for (size_t i = 0; i < JsonLength(source); i++)
3553             {
3554                 JsonArrayAppendElement(dest, JsonExpandElement(ctx, JsonArrayGet(source, i)));
3555             }
3556             return dest;
3557         }
3558     }
3559 
3560     ProgrammingError("JsonExpandElement: unexpected container type");
3561     return NULL;
3562 }
3563 
EvalContextAllClassesGet(const EvalContext * ctx)3564 const StringSet *EvalContextAllClassesGet(const EvalContext *ctx)
3565 {
3566     assert (ctx);
3567     return ctx->all_classes;
3568 }
3569 
EvalContextAllClassesLoggingEnable(EvalContext * ctx,bool enable)3570 void EvalContextAllClassesLoggingEnable(EvalContext *ctx, bool enable)
3571 {
3572     assert (ctx);
3573     Nova_ClassHistoryEnable(&(ctx->all_classes), enable);
3574 }
3575 
EvalContextPushBundleName(const EvalContext * ctx,const char * bundle_name)3576 void EvalContextPushBundleName(const EvalContext *ctx, const char *bundle_name)
3577 {
3578     assert (ctx);
3579     StringSetAdd(ctx->bundle_names, xstrdup(bundle_name));
3580 }
3581 
EvalContextGetBundleNames(const EvalContext * ctx)3582 const StringSet *EvalContextGetBundleNames(const EvalContext *ctx)
3583 {
3584     assert (ctx);
3585     return ctx->bundle_names;
3586 }
3587 
EvalContextPushRemoteVarPromise(EvalContext * ctx,const char * bundle_name,const Promise * pp)3588 void EvalContextPushRemoteVarPromise(EvalContext *ctx, const char *bundle_name, const Promise *pp)
3589 {
3590     assert (ctx);
3591 
3592     /* initiliaze the map if needed */
3593     if (ctx->remote_var_promises == NULL)
3594     {
3595         ctx->remote_var_promises = RemoteVarPromisesMapNew();
3596     }
3597 
3598     Seq *promises = RemoteVarPromisesMapGet(ctx->remote_var_promises, bundle_name);
3599     if (promises == NULL)
3600     {
3601         /* initialize the sequence if needed */
3602         /* ItemDestroy == NULL because we need to store the exact pointers not
3603          * copies */
3604         promises = SeqNew(10, NULL);
3605         RemoteVarPromisesMapInsert(ctx->remote_var_promises, xstrdup(bundle_name), promises);
3606     }
3607     /* intentionally not making a copy here, we need the exact pointer */
3608     SeqAppend(promises, (void *) pp);
3609 }
3610 
EvalContextGetRemoteVarPromises(const EvalContext * ctx,const char * bundle_name)3611 const Seq *EvalContextGetRemoteVarPromises(const EvalContext *ctx, const char *bundle_name)
3612 {
3613     assert (ctx);
3614     if (ctx->remote_var_promises == NULL)
3615     {
3616         return NULL;
3617     }
3618     return RemoteVarPromisesMapGet(ctx->remote_var_promises, bundle_name);
3619 }
3620 
EvalContextSetDumpReports(EvalContext * ctx,bool dump_reports)3621 void EvalContextSetDumpReports(EvalContext *ctx, bool dump_reports)
3622 {
3623     assert(ctx != NULL);
3624     ctx->dump_reports = dump_reports;
3625     if (dump_reports)
3626     {
3627         Log(LOG_LEVEL_VERBOSE, "Report dumping is enabled");
3628     }
3629 }
3630 
EvalContextGetDumpReports(EvalContext * ctx)3631 bool EvalContextGetDumpReports(EvalContext *ctx)
3632 {
3633     assert(ctx != NULL);
3634 
3635     return ctx->dump_reports;
3636 }
3637 
EvalContextUpdateDumpReports(EvalContext * ctx)3638 void EvalContextUpdateDumpReports(EvalContext *ctx)
3639 {
3640     assert(ctx != NULL);
3641 
3642     char enable_file_path[PATH_MAX];
3643     snprintf(
3644         enable_file_path,
3645         PATH_MAX,
3646         "%s%cenable_report_dumps",
3647         GetWorkDir(),
3648         FILE_SEPARATOR);
3649     EvalContextSetDumpReports(ctx, (access(enable_file_path, F_OK) == 0));
3650 }
3651 
3652 static char chrooted_path[PATH_MAX + 1] = {0};
3653 static size_t chroot_len = 0;
SetChangesChroot(const char * chroot)3654 void SetChangesChroot(const char *chroot)
3655 {
3656     assert(chroot != NULL);
3657 
3658     /* This function should only be called once. */
3659     assert(chroot_len == 0);
3660 
3661     chroot_len = SafeStringLength(chroot);
3662 
3663     memcpy(chrooted_path, chroot, chroot_len);
3664 
3665     /* Make sure there is a file separator at the end. */
3666     if (!IsFileSep(chroot[chroot_len - 1]))
3667     {
3668         chroot_len++;
3669         chrooted_path[chroot_len - 1] = FILE_SEPARATOR;
3670     }
3671 }
3672 
ToChangesChroot(const char * orig_path)3673 const char *ToChangesChroot(const char *orig_path)
3674 {
3675     /* SetChangesChroot() should be called first. */
3676     assert(chroot_len != 0);
3677 
3678     assert(orig_path != NULL);
3679     assert(IsAbsPath(orig_path));
3680     assert(strlen(orig_path) <= (PATH_MAX - chroot_len - 1));
3681 
3682     size_t offset = 0;
3683 #ifdef __MINGW32__
3684     /* On Windows, absolute path starts with the drive letter and colon followed
3685      * by '\'. Let's replace the ":\" with just "\" so that each drive has its
3686      * own directory tree in the chroot. */
3687     if ((orig_path[0] > 'A') && ((orig_path[0] < 'Z')) && (orig_path[1] == ':'))
3688     {
3689         chrooted_path[chroot_len] = orig_path[0];
3690         chrooted_path[chroot_len + 1] = FILE_SEPARATOR;
3691         orig_path += 2;
3692         offset += 2;
3693     }
3694 #endif
3695 
3696     while (orig_path[0] == FILE_SEPARATOR)
3697     {
3698         orig_path++;
3699     }
3700 
3701     /* Adds/copies the NUL-byte at the end of the string. */
3702     strncpy(chrooted_path + chroot_len + offset, orig_path, (PATH_MAX - chroot_len - offset - 1));
3703 
3704     return chrooted_path;
3705 }
3706 
ToNormalRoot(const char * orig_path)3707 const char *ToNormalRoot(const char *orig_path)
3708 {
3709     assert(strncmp(orig_path, chrooted_path, chroot_len) == 0);
3710 
3711     return orig_path + chroot_len - 1;
3712 }
3713