1 /*
2   Copyright 2021 Northern.tech AS
3 
4   This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5 
6   This program is free software; you can redistribute it and/or modify it
7   under the terms of the GNU General Public License as published by the
8   Free Software Foundation; version 3.
9 
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
18 
19   To the extent this program is licensed as part of the Enterprise
20   versions of CFEngine, the applicable Commercial Open Source License
21   (COSL) may apply to this file if you as a licensee so wish it. See
22   included file COSL.txt.
23 */
24 
25 #include <verify_packages.h>
26 #include <verify_new_packages.h>
27 #include <package_module.h>
28 
29 #include <actuator.h>
30 #include <promises.h>
31 #include <dir.h>
32 #include <files_names.h>
33 #include <files_interfaces.h>
34 #include <file_lib.h>
35 #include <vars.h>
36 #include <conversion.h>
37 #include <expand.h>
38 #include <scope.h>
39 #include <vercmp.h>
40 #include <matching.h>
41 #include <match_scope.h>
42 #include <attributes.h>
43 #include <string_lib.h>
44 #include <pipes.h>
45 #include <locks.h>
46 #include <exec_tools.h>
47 #include <policy.h>
48 #include <misc_lib.h>
49 #include <rlist.h>
50 #include <ornaments.h>
51 #include <eval_context.h>
52 #include <retcode.h>
53 #include <known_dirs.h>
54 #include <csv_writer.h>
55 #include <cf-agent-enterprise-stubs.h>
56 #include <cf-windows-functions.h>
57 
58 /* Called structure:
59 
60    Top-level: cf-agent calls...
61 
62    * CleanScheduledPackages
63 
64    * VerifyPackagesPromise -> all the Verify* functions that schedule operation
65 
66    * ExecuteScheduledPackages -> all the Execute* functions to run operations
67 
68  */
69 
70 /** Entry points from VerifyPackagesPromise **/
71 
72 #define REPORT_THIS_PROMISE(__pp) (strncmp(__pp->promiser, "cfe_internal_", 13) != 0)
73 
74 #define cfPS_HELPER_0ARG(__ctx, __log_level, __result, __pp, __a, __str) \
75     if (REPORT_THIS_PROMISE(__pp)) \
76     { \
77         cfPS(__ctx, __log_level, __result, __pp, __a, __str); \
78     }
79 #define cfPS_HELPER_1ARG(__ctx, __log_level, __result, __pp, __a, __str, __arg1) \
80     if (REPORT_THIS_PROMISE(__pp)) \
81     { \
82         cfPS(__ctx, __log_level, __result, __pp, __a, __str, __arg1); \
83     }
84 #define cfPS_HELPER_2ARG(__ctx, __log_level, __result, __pp, __a, __str, __arg1, __arg2) \
85     if (REPORT_THIS_PROMISE(__pp)) \
86     { \
87         cfPS(__ctx, __log_level, __result, __pp, __a, __str, __arg1, __arg2); \
88     }
89 #define cfPS_HELPER_3ARG(__ctx, __log_level, __result, __pp, __a, __str, __arg1, __arg2, __arg3) \
90     if (REPORT_THIS_PROMISE(__pp)) \
91     { \
92         cfPS(__ctx, __log_level, __result, __pp, __a, __str, __arg1, __arg2, __arg3); \
93     }
94 
95 #define PromiseResultUpdate_HELPER(__pp, __prior, __evidence) \
96     REPORT_THIS_PROMISE(__pp) ? PromiseResultUpdate(__prior, __evidence) : __evidence
97 
98 typedef enum
99 {
100     PACKAGE_PROMISE_TYPE_OLD = 0,
101     PACKAGE_PROMISE_TYPE_NEW,
102     PACKAGE_PROMISE_TYPE_MIXED,
103     PACKAGE_PROMISE_TYPE_OLD_ERROR,
104     PACKAGE_PROMISE_TYPE_NEW_ERROR
105 } PackagePromiseType;
106 
107 static bool PackageSanityCheck(EvalContext *ctx, const Attributes *a, const Promise *pp);
108 
109 static bool VerifyInstalledPackages(EvalContext *ctx, PackageManager **alllists, const char *default_arch, const Attributes *a, const Promise *pp, PromiseResult *result);
110 
111 static PromiseResult VerifyPromisedPackage(EvalContext *ctx, const Attributes *a, const Promise *pp);
112 static PromiseResult VerifyPromisedPatch(EvalContext *ctx, const Attributes *a, const Promise *pp);
113 
114 /** Utils **/
115 
116 static char *GetDefaultArch(const char *command);
117 
118 static bool ExecPackageCommand(EvalContext *ctx, char *command, int verify, int setCmdClasses, const Attributes *a, const Promise *pp, PromiseResult *result);
119 
120 static bool PrependPatchItem(EvalContext *ctx, PackageItem ** list, char *item, PackageItem * chklist, const char *default_arch, const Attributes *a, const Promise *pp);
121 static bool PrependMultiLinePackageItem(EvalContext *ctx, PackageItem ** list, char *item, int reset, const char *default_arch, const Attributes *a, const Promise *pp);
122 static bool PrependListPackageItem(EvalContext *ctx, PackageItem ** list, char *item, const char *default_arch, const Attributes *a, const Promise *pp);
123 
124 static PackageManager *GetPackageManager(PackageManager **lists, char *mgr, PackageAction pa, PackageActionPolicy x);
125 static void DeletePackageManagers(PackageManager *morituri);
126 
127 static const char *PrefixLocalRepository(const Rlist *repositories, const char *package);
128 
129 static PromiseResult HandleOldPackagePromiseType(EvalContext *ctx, const Promise *pp, const Attributes *a);
130 
ENTERPRISE_VOID_FUNC_1ARG_DEFINE_STUB(void,ReportPatches,ARG_UNUSED PackageManager *,list)131 ENTERPRISE_VOID_FUNC_1ARG_DEFINE_STUB(void, ReportPatches, ARG_UNUSED PackageManager *, list)
132 {
133     Log(LOG_LEVEL_VERBOSE, "Patch reporting feature is only available in the enterprise version");
134 }
135 
136 /*****************************************************************************/
137 
138 PackageManager *PACKAGE_SCHEDULE = NULL; /* GLOBAL_X */
139 PackageManager *INSTALLED_PACKAGE_LISTS = NULL; /* GLOBAL_X */
140 
141 #define PACKAGE_LIST_COMMAND_WINAPI "/Windows_API"
142 
143 /*****************************************************************************/
144 
145 #define PACKAGE_IGNORED_CFE_INTERNAL "cfe_internal_non_existing_package"
146 
147 /* Returns the old or new package promise type depending on promise
148    constraints. */
GetPackagePromiseVersion(const Packages * packages,const NewPackages * new_packages)149 static PackagePromiseType GetPackagePromiseVersion(const Packages *packages,
150         const NewPackages *new_packages)
151 {
152     assert(packages != NULL);
153     assert(new_packages != NULL);
154 
155     /* We have mixed packages promise constraints. */
156     if (!packages->is_empty && !new_packages->is_empty)
157     {
158         return PACKAGE_PROMISE_TYPE_MIXED;
159     }
160     else if (!new_packages->is_empty) /* new packages promise */
161     {
162         if (new_packages->package_policy == NEW_PACKAGE_ACTION_NONE)
163         {
164             return PACKAGE_PROMISE_TYPE_NEW_ERROR;
165         }
166         return PACKAGE_PROMISE_TYPE_NEW;
167     }
168     else /* old packages promise */
169     {
170         //TODO:
171         if (!packages->has_package_method)
172         {
173             return PACKAGE_PROMISE_TYPE_OLD_ERROR;
174         }
175         return PACKAGE_PROMISE_TYPE_OLD;
176     }
177 }
178 
VerifyPackagesPromise(EvalContext * ctx,const Promise * pp)179 PromiseResult VerifyPackagesPromise(EvalContext *ctx, const Promise *pp)
180 {
181     assert(pp != NULL); // Dereferenced in cfPS macros
182 
183     PromiseResult result = PROMISE_RESULT_FAIL;
184 
185     Attributes a = GetPackageAttributes(ctx, pp);
186     PackagePromiseType package_promise_type =
187             GetPackagePromiseVersion(&a.packages, &a.new_packages);
188 
189     switch (package_promise_type)
190     {
191         case PACKAGE_PROMISE_TYPE_NEW:
192             Log(LOG_LEVEL_VERBOSE, "Using new package promise.");
193 
194             result = HandleNewPackagePromiseType(ctx, pp, &a);
195             break;
196 
197         case PACKAGE_PROMISE_TYPE_OLD:
198             Log(LOG_LEVEL_VERBOSE,
199                 "Using old package promise. Please note that this old "
200                 "implementation is being phased out. The old "
201                 "implementation will continue to work, but forward development "
202                 "will be directed toward the new implementation.");
203 
204             result = HandleOldPackagePromiseType(ctx, pp, &a);
205 
206             /* Update new package promise cache in case we have mixed old and new
207              * package promises in policy. */
208             if (result == PROMISE_RESULT_CHANGE || result == PROMISE_RESULT_FAIL)
209             {
210                 UpdatePackagesCache(ctx, false);
211             }
212             break;
213         case PACKAGE_PROMISE_TYPE_NEW_ERROR:
214             cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, &a,
215                          "New package promise failed sanity check.");
216             break;
217         case PACKAGE_PROMISE_TYPE_OLD_ERROR:
218             cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, &a,
219                          "Old package promise failed sanity check.");
220             break;
221         case PACKAGE_PROMISE_TYPE_MIXED:
222             cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, &a,
223                          "Mixed old and new package promise attributes inside "
224                          "one package promise.");
225             break;
226         default:
227             assert(0); //Shouldn't happen
228     }
229     return result;
230 }
231 
232 
233 /******************************************************************************/
234 
235 /**
236    @brief Executes single packages promise
237 
238    Called by cf-agent.
239 
240    * checks "name", "version", "arch", "firstrepo" variables from the "this" context
241    * gets the package attributes into a
242    * on Windows, if the package_list_command is not defined, use the hard-coded PACKAGE_LIST_COMMAND_WINAPI
243    * do a package sanity check on the promise
244    * print promise banner
245    * reset to root directory (Yum bugfix)
246    * get the default architecture from a.packages.package_default_arch_command into default_arch
247    * call VerifyInstalledPackages with default_arch
248    * if the package action is "patch", call VerifyPromisedPatch and return its result through PromiseResultUpdate_HELPER
249    * for all other package actions, call VerifyPromisedPackage and return its result through PromiseResultUpdate_HELPER
250 
251    @param ctx [in] The evaluation context
252    @param pp [in] the Promise for this operation
253    @returns the promise result
254 */
HandleOldPackagePromiseType(EvalContext * ctx,const Promise * pp,const Attributes * attr)255 static PromiseResult HandleOldPackagePromiseType(EvalContext *ctx, const Promise *pp, const Attributes *attr)
256 {
257     assert(attr != NULL);
258     assert(pp != NULL);
259 
260     Attributes a = *attr; // TODO: Remove this local copy, overwritten on windows
261     CfLock thislock;
262     char lockname[CF_BUFSIZE];
263     PromiseResult result = PROMISE_RESULT_NOOP;
264 
265     const char *reserved_vars[] = { "name", "version", "arch", "firstrepo", NULL };
266     for (int c = 0; reserved_vars[c]; c++)
267     {
268         const char *reserved = reserved_vars[c];
269         VarRef *var_ref = VarRefParseFromScope(reserved, "this");
270         if (EvalContextVariableGet(ctx, var_ref, NULL))
271         {
272             Log(LOG_LEVEL_WARNING, "$(%s) variable has a special meaning in packages promises. "
273                 "Things may not work as expected if it is already defined.", reserved);
274         }
275         VarRefDestroy(var_ref);
276     }
277 
278 #ifdef __MINGW32__
279 
280     if(!a.packages.package_list_command)
281     {
282         a.packages.package_list_command = PACKAGE_LIST_COMMAND_WINAPI;
283     }
284 
285 #endif
286 
287     if (!PackageSanityCheck(ctx, &a, pp))
288     {
289         Log(LOG_LEVEL_VERBOSE, "Package promise %s failed sanity check", pp->promiser);
290         result = PROMISE_RESULT_FAIL;
291         goto end;
292     }
293 
294     PromiseBanner(ctx, pp);
295 
296 // Now verify the package itself
297 
298     PackagePromiseGlobalLock package_lock = AcquireGlobalPackagePromiseLock(ctx);
299     if (package_lock.g_lock.lock == NULL)
300     {
301         Log(LOG_LEVEL_VERBOSE,
302             "Can not acquire global lock for package promise. Skipping promise "
303             "evaluation");
304         result = PROMISE_RESULT_SKIPPED;
305         goto end;
306     }
307 
308     snprintf(lockname, CF_BUFSIZE - 1, "package-%s-%s", pp->promiser, a.packages.package_list_command);
309 
310     thislock = AcquireLock(ctx, lockname, VUQNAME, CFSTARTTIME,
311         a.transaction.ifelapsed, a.transaction.expireafter, pp, false);
312     if (thislock.lock == NULL)
313     {
314         YieldGlobalPackagePromiseLock(package_lock);
315         result = PROMISE_RESULT_SKIPPED;
316         goto end;
317     }
318 
319 // Start by reseting the root directory in case yum tries to glob regexs(!)
320 
321     if (safe_chdir("/") != 0)
322     {
323         Log(LOG_LEVEL_ERR, "Failed to chdir into '/'");
324     }
325 
326     char *default_arch = GetDefaultArch(a.packages.package_default_arch_command);
327 
328     if (default_arch == NULL)
329     {
330         cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, &a, "Unable to obtain default architecture for package manager - aborting");
331         YieldCurrentLock(thislock);
332         YieldGlobalPackagePromiseLock(package_lock);
333         result = PROMISE_RESULT_FAIL;
334         goto end;
335     }
336 
337     Log(LOG_LEVEL_VERBOSE, "Default package architecture for promise %s is '%s'", pp->promiser, default_arch);
338     if (!VerifyInstalledPackages(ctx, &INSTALLED_PACKAGE_LISTS, default_arch, &a, pp, &result))
339     {
340         cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, &a, "Unable to obtain a list of installed packages - aborting");
341         free(default_arch);
342         YieldCurrentLock(thislock);
343         YieldGlobalPackagePromiseLock(package_lock);
344         result = PROMISE_RESULT_FAIL;
345         goto end;
346     }
347 
348     free(default_arch);
349 
350     if (a.packages.package_policy == PACKAGE_ACTION_PATCH)
351     {
352         Log(LOG_LEVEL_VERBOSE, "Verifying patch action for promise %s", pp->promiser);
353         result = PromiseResultUpdate_HELPER(pp, result, VerifyPromisedPatch(ctx, &a, pp));
354     }
355     else
356     {
357         Log(LOG_LEVEL_VERBOSE, "Verifying action for promise %s", pp->promiser);
358         result = PromiseResultUpdate_HELPER(pp, result, VerifyPromisedPackage(ctx, &a, pp));
359     }
360 
361     YieldCurrentLock(thislock);
362     YieldGlobalPackagePromiseLock(package_lock);
363 
364 end:
365     if (!REPORT_THIS_PROMISE(pp))
366     {
367         // This will not be reported elsewhere, so give it kept outcome.
368         result = PROMISE_RESULT_NOOP;
369         cfPS(ctx, LOG_LEVEL_DEBUG, result, pp, &a, "Giving dummy package kept outcome");
370     }
371 
372     return result;
373 }
374 
375 /**
376    @brief Pre-check of promise contents
377 
378    Called by VerifyPackagesPromise.  Does many sanity checks on the
379    promise attributes and semantics.
380 
381    @param ctx [in] The evaluation context
382    @param a [in] the promise Attributes for this operation
383    @param pp [in] the Promise for this operation
384    @returns the promise result
385 */
386 
PackageSanityCheck(EvalContext * ctx,const Attributes * a,const Promise * pp)387 static bool PackageSanityCheck(EvalContext *ctx, const Attributes *a, const Promise *pp)
388 {
389     assert(a != NULL);
390     assert(pp != NULL); // Dereferenced in cfPS macros
391 
392     const Packages *const pkgs = &(a->packages);
393 #ifndef __MINGW32__  // Windows may use Win32 API for listing and parsing
394 
395     if (pkgs->package_list_name_regex == NULL)
396     {
397         cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
398              "You must supply a method for determining the name of existing packages e.g. use the standard library generic package_method");
399         return false;
400     }
401 
402     if (pkgs->package_list_version_regex == NULL)
403     {
404         cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
405              "You must supply a method for determining the version of existing packages e.g. use the standard library generic package_method");
406         return false;
407     }
408 
409     if ((!pkgs->package_commands_useshell) && (pkgs->package_list_command) && (!IsExecutable(CommandArg0(pkgs->package_list_command))))
410     {
411         cfPS_HELPER_1ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
412              "The proposed package list command '%s' was not executable",
413              pkgs->package_list_command);
414         return false;
415     }
416 
417 
418 #endif /* !__MINGW32__ */
419 
420 
421     if ((pkgs->package_list_command == NULL) && (pkgs->package_file_repositories == NULL))
422     {
423         cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
424              "You must supply a method for determining the list of existing packages (a command or repository list) e.g. use the standard library generic package_method");
425         return false;
426     }
427 
428     if (pkgs->package_file_repositories)
429     {
430         Rlist *rp;
431 
432         for (rp = pkgs->package_file_repositories; rp != NULL; rp = rp->next)
433         {
434             if (strlen(RlistScalarValue(rp)) > CF_MAXVARSIZE - 1)
435             {
436                 cfPS_HELPER_1ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "The repository path '%s' is too long", RlistScalarValue(rp));
437                 return false;
438             }
439         }
440     }
441 
442     if ((pkgs->package_name_regex) || (pkgs->package_version_regex) || (pkgs->package_arch_regex))
443     {
444         if (pkgs->package_name_regex == NULL)
445         {
446             cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "You must supply name regex if you supplied version or arch regex for parsing promiser string");
447             return false;
448         }
449         if ((pkgs->package_name_regex) && (pkgs->package_version_regex) && (pkgs->package_arch_regex))
450         {
451             if ((pkgs->package_version) || (pkgs->package_architectures))
452             {
453                 cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
454                      "You must either supply all regexs for (name,version,arch) or a separate version number and architecture");
455                 return false;
456             }
457         }
458         else
459         {
460             if ((pkgs->package_version) && (pkgs->package_architectures))
461             {
462                 cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
463                      "You must either supply all regexs for (name,version,arch) or a separate version number and architecture");
464                 return false;
465             }
466         }
467 
468         if ((pkgs->package_version_regex) && (pkgs->package_version))
469         {
470             cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
471                  "You must either supply version regex or a separate version number");
472             return false;
473         }
474 
475         if ((pkgs->package_arch_regex) && (pkgs->package_architectures))
476         {
477             cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
478                  "You must either supply arch regex or a separate architecture");
479             return false;
480         }
481     }
482 
483     if (!pkgs->package_installed_regex)
484     {
485         cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "!! Package installed regex undefined");
486         return false;
487     }
488 
489     if (pkgs->package_policy == PACKAGE_ACTION_VERIFY)
490     {
491         if (!pkgs->package_verify_command)
492         {
493             cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
494                  "!! Package verify policy is used, but no package_verify_command is defined");
495             return false;
496         }
497         else if ((pkgs->package_noverify_returncode == CF_NOINT) && (pkgs->package_noverify_regex == NULL))
498         {
499             cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
500                  "!! Package verify policy is used, but no definition of verification failiure is set (package_noverify_returncode or packages.package_noverify_regex)");
501             return false;
502         }
503     }
504 
505     if ((pkgs->package_noverify_returncode != CF_NOINT) && (pkgs->package_noverify_regex))
506     {
507         cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
508              "!! Both package_noverify_returncode and package_noverify_regex are defined, pick one of them");
509         return false;
510     }
511 
512     /* Dependency checks */
513     if (!pkgs->package_delete_command)
514     {
515         if (pkgs->package_delete_convention)
516         {
517             cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
518                  "!! Dependency conflict: package_delete_command is not used, but package_delete_convention is defined.");
519             return false;
520         }
521     }
522     if (!pkgs->package_list_command)
523     {
524         if (pkgs->package_installed_regex)
525         {
526             cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
527                  "!! Dependency conflict: package_list_command is not used, but package_installed_regex is defined.");
528             return false;
529         }
530         if (pkgs->package_list_arch_regex)
531         {
532             cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
533                  "!! Dependency conflict: package_list_command is not used, but package_arch_regex is defined.");
534             return false;
535         }
536         if (pkgs->package_list_name_regex)
537         {
538             cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
539                  "!! Dependency conflict: package_list_command is not used, but package_name_regex is defined.");
540             return false;
541         }
542         if (pkgs->package_list_version_regex)
543         {
544             cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
545                  "!! Dependency conflict: package_list_command is not used, but package_version_regex is defined.");
546             return false;
547         }
548     }
549     if (!pkgs->package_patch_command)
550     {
551         if (pkgs->package_patch_arch_regex)
552         {
553             cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
554                  "!! Dependency conflict: package_patch_command is not used, but package_patch_arch_regex is defined.");
555             return false;
556         }
557         if (pkgs->package_patch_name_regex)
558         {
559             cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
560                  "!! Dependency conflict: package_patch_command is not used, but package_patch_name_regex is defined.");
561             return false;
562         }
563         if (pkgs->package_patch_version_regex)
564         {
565             cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
566                  "!! Dependency conflict: package_patch_command is not used, but package_patch_version_regex is defined.");
567             return false;
568         }
569     }
570     if (!pkgs->package_patch_list_command)
571     {
572         if (pkgs->package_patch_installed_regex)
573         {
574             cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
575                  "!! Dependency conflict: package_patch_list_command is not used, but package_patch_installed_regex is defined.");
576             return false;
577         }
578     }
579     if (!pkgs->package_verify_command)
580     {
581         if (pkgs->package_noverify_regex)
582         {
583             cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
584                  "!! Dependency conflict: package_verify_command is not used, but package_noverify_regex is defined.");
585             return false;
586         }
587         if (pkgs->package_noverify_returncode != CF_NOINT)
588         {
589             cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
590                  "!! Dependency conflict: package_verify_command is not used, but package_noverify_returncode is defined.");
591             return false;
592         }
593     }
594     return true;
595 }
596 
597 /**
598    @brief Generates the list of installed packages
599 
600    Called by VerifyInstalledPackages
601 
602    * calls pkgs->package_list_update_command if $(sys.statedir)/software_update_timestamp_<manager>
603      is older than the interval specified in package_list_update_ifelapsed.
604    * assembles the package list from a.packages.package_list_command
605    * respects a.packages.package_commands_useshell (boolean)
606    * parses with a.packages.package_multiline_start and if successful, calls PrependMultiLinePackageItem
607    * else, parses with a.packages.package_installed_regex and if successful, calls PrependListPackageItem
608 
609    @param ctx [in] The evaluation context
610    @param installed_list [in] a list of PackageItems
611    @param default_arch [in] the default architecture
612    @param a [in] the promise Attributes for this operation
613    @param pp [in] the Promise for this operation
614    @param result [inout] the PromiseResult for this operation
615    @returns boolean pass/fail of command run
616 */
PackageListInstalledFromCommand(EvalContext * ctx,PackageItem ** installed_list,const char * default_arch,const Attributes * a,const Promise * pp,PromiseResult * result)617 static bool PackageListInstalledFromCommand(EvalContext *ctx,
618                                             PackageItem **installed_list,
619                                             const char *default_arch,
620                                             const Attributes *a, const Promise *pp,
621                                             PromiseResult *result)
622 {
623     assert(a != NULL);
624 
625     if (a->packages.package_list_update_command != NULL)
626     {
627         if (!a->packages.package_add_command)
628         {
629             Log(LOG_LEVEL_ERR, "package_add_command missing while trying to "
630                                "generate list of installed packages");
631             return false;
632         }
633 
634         time_t horizon = 24 * 60, now = time(NULL);
635         bool call_update = true;
636         struct stat sb;
637         char update_timestamp_file[PATH_MAX];
638 
639         snprintf(update_timestamp_file, sizeof(update_timestamp_file), "%s%csoftware_update_timestamp_%s",
640                  GetStateDir(), FILE_SEPARATOR,
641                  ReadLastNode(RealPackageManager(a->packages.package_add_command)));
642 
643         if (stat(update_timestamp_file, &sb) != -1)
644         {
645             if (a->packages.package_list_update_ifelapsed != CF_NOINT)
646             {
647                 horizon = a->packages.package_list_update_ifelapsed;
648             }
649 
650             char *rel, *action;
651             if (now - sb.st_mtime < horizon * 60)
652             {
653                 rel = "less";
654                 action = "Not updating";
655                 call_update = false;
656             }
657             else
658             {
659                 rel = "more";
660                 action = "Updating";
661             }
662             Log(LOG_LEVEL_VERBOSE, "'%s' is %s than %i minutes old. %s package list.",
663                 update_timestamp_file, rel, a->packages.package_list_update_ifelapsed, action);
664         }
665         else
666         {
667             Log(LOG_LEVEL_VERBOSE, "'%s' does not exist. Updating package list.", update_timestamp_file);
668         }
669 
670         if (call_update)
671         {
672             Log(LOG_LEVEL_VERBOSE, "Calling package list update command: '%s'", a->packages.package_list_update_command);
673             ExecPackageCommand(ctx, a->packages.package_list_update_command, false, false, a, pp, result);
674 
675             // Touch timestamp file.
676             int err = utime(update_timestamp_file, NULL);
677             if (err < 0)
678             {
679                 if (errno == ENOENT)
680                 {
681                     int fd = open(update_timestamp_file, O_WRONLY | O_CREAT, 0600);
682                     if (fd >= 0)
683                     {
684                         close(fd);
685                     }
686                     else
687                     {
688                         Log(LOG_LEVEL_ERR, "Could not create timestamp file '%s'. (open: '%s')",
689                             update_timestamp_file, GetErrorStr());
690                     }
691                 }
692                 else
693                 {
694                     Log(LOG_LEVEL_ERR, "Could not update timestamp file '%s'. (utime: '%s')",
695                         update_timestamp_file, GetErrorStr());
696                 }
697             }
698         }
699     }
700 
701     Log(LOG_LEVEL_VERBOSE, "Reading package list from '%s'", a->packages.package_list_command);
702 
703     FILE *fin;
704 
705     if (a->packages.package_commands_useshell)
706     {
707         if ((fin = cf_popen_sh(a->packages.package_list_command, "r")) == NULL)
708         {
709             Log(LOG_LEVEL_ERR, "Couldn't open the package list with command '%s'. (cf_popen_sh: %s)",
710                   a->packages.package_list_command, GetErrorStr());
711             return false;
712         }
713     }
714     else if ((fin = cf_popen(a->packages.package_list_command, "r", true)) == NULL)
715     {
716         Log(LOG_LEVEL_ERR, "Couldn't open the package list with command '%s'. (cf_popen: %s)",
717             a->packages.package_list_command, GetErrorStr());
718         return false;
719     }
720 
721     const int reset = true, update = false;
722 
723     size_t buf_size = CF_BUFSIZE;
724     char *buf = xmalloc(buf_size);
725 
726     for (;;)
727     {
728         ssize_t res = CfReadLine(&buf, &buf_size, fin);
729         if (res == -1)
730         {
731             if (!feof(fin))
732             {
733                 Log(LOG_LEVEL_ERR, "Unable to read list of packages from command '%s'. (fread: %s)",
734                       a->packages.package_list_command, GetErrorStr());
735                 cf_pclose(fin);
736                 free(buf);
737                 return false;
738             }
739             else
740             {
741                 break;
742             }
743         }
744 
745         if (a->packages.package_multiline_start)
746         {
747             if (FullTextMatch(ctx, a->packages.package_multiline_start, buf))
748             {
749                 PrependMultiLinePackageItem(ctx, installed_list, buf, reset, default_arch, a, pp);
750             }
751             else
752             {
753                 PrependMultiLinePackageItem(ctx, installed_list, buf, update, default_arch, a, pp);
754             }
755         }
756         else
757         {
758             if (!FullTextMatch(ctx, a->packages.package_installed_regex, buf))
759             {
760                 Log(LOG_LEVEL_VERBOSE, "Package line '%s' did not match the package_installed_regex pattern", buf);
761                 continue;
762             }
763 
764             if (!PrependListPackageItem(ctx, installed_list, buf, default_arch, a, pp))
765             {
766                 Log(LOG_LEVEL_VERBOSE, "Package line '%s' did not match one of the package_list_(name|version|arch)_regex patterns", buf);
767                 continue;
768             }
769 
770         }
771     }
772 
773     if (a->packages.package_multiline_start)
774     {
775         PrependMultiLinePackageItem(ctx, installed_list, buf, reset, default_arch, a, pp);
776     }
777 
778     free(buf);
779     return cf_pclose(fin) == 0;
780 }
781 
782 /**
783    @brief Writes the software inventory
784 
785    Called by VerifyInstalledPackages
786 
787    * calls GetSoftwareCacheFilename to get the inventory CSV filename
788    * for each PackageManager in the list
789    *  * for each PackageItem in the PackageManager's list
790    *  * write name, version, architecture, manager name
791 
792    @param ctx [in] The evaluation context
793    @param list [in] a list of PackageManagers
794 */
ReportSoftware(PackageManager * list)795 static void ReportSoftware(PackageManager *list)
796 {
797     PackageManager *mp = NULL;
798     PackageItem *pi;
799     char name[CF_BUFSIZE];
800 
801     GetSoftwareCacheFilename(name);
802 
803     FILE *fout = safe_fopen(name, "w");
804     if (fout == NULL)
805     {
806         Log(LOG_LEVEL_ERR,
807             "Cannot open the destination file '%s'. (fopen: %s)",
808             name, GetErrorStr());
809         return;
810     }
811 
812     Writer *writer_installed = FileWriter(fout);
813 
814     CsvWriter *c = CsvWriterOpen(writer_installed);
815     if (c)
816     {
817         for (mp = list; mp != NULL; mp = mp->next)
818         {
819             for (pi = mp->pack_list; pi != NULL; pi = pi->next)
820             {
821                 CsvWriterField(c, pi->name);
822                 CsvWriterField(c, pi->version);
823                 CsvWriterField(c, pi->arch);
824                 CsvWriterField(c, ReadLastNode(RealPackageManager(mp->manager)));
825                 CsvWriterNewRecord(c);
826             }
827         }
828 
829         CsvWriterClose(c);
830     }
831     else
832     {
833         Log(LOG_LEVEL_ERR, "Cannot write CSV to file '%s'", name);
834     }
835 
836     WriterClose(writer_installed);
837 }
838 
839 /**
840    @brief Invalidates the software inventory
841 
842    Called by ExecuteSchedule and ExecutePatch
843 
844    * calls GetSoftwareCacheFilename to get the inventory CSV filename
845    * sets atime and mtime on that file to 0
846 */
InvalidateSoftwareCache(void)847 static void InvalidateSoftwareCache(void)
848 {
849     char name[CF_BUFSIZE];
850     struct utimbuf epoch = { 0, 0 };
851 
852     GetSoftwareCacheFilename(name);
853 
854     if (utime(name, &epoch) != 0)
855     {
856         if (errno != ENOENT)
857         {
858             Log(LOG_LEVEL_ERR, "Cannot mark software cache as invalid. (utimes: %s)", GetErrorStr());
859         }
860     }
861 }
862 
863 /**
864    @brief Gets the cached list of installed packages from file
865 
866    Called by VerifyInstalledPackages
867 
868    * calls GetSoftwareCacheFilename to get the inventory CSV filename
869    * respects a.packages.package_list_update_ifelapsed, returns NULL if file is too old
870    * parses the CSV out of the file (name, version, arch, manager) with each limited to 250 chars
871    * for each line
872    * * if architecture is "default", replace it with default_arch
873    * * if the package manager name matches, call PrependPackageItem
874 
875    @param ctx [in] The evaluation context
876    @param manager [in] the PackageManager we want
877    @param default_arch [in] the default architecture
878    @param a [in] the promise Attributes for this operation
879    @param pp [in] the Promise for this operation
880    @returns list of PackageItems
881 */
GetCachedPackageList(EvalContext * ctx,PackageManager * manager,const char * default_arch,const Attributes * a,const Promise * pp)882 static PackageItem *GetCachedPackageList(EvalContext *ctx, PackageManager *manager, const char *default_arch, const Attributes *a,
883                                          const Promise *pp)
884 {
885     assert(a != NULL);
886     assert(manager != NULL);
887 
888     PackageItem *list = NULL;
889     char name[CF_MAXVARSIZE], version[CF_MAXVARSIZE], arch[CF_MAXVARSIZE], mgr[CF_MAXVARSIZE], line[CF_BUFSIZE];
890     char thismanager[CF_MAXVARSIZE];
891     FILE *fin;
892     time_t horizon = 24 * 60, now = time(NULL);
893     struct stat sb;
894 
895     GetSoftwareCacheFilename(name);
896 
897     if (stat(name, &sb) == -1)
898     {
899         return NULL;
900     }
901 
902     if (a->packages.package_list_update_ifelapsed != CF_NOINT)
903     {
904         horizon = a->packages.package_list_update_ifelapsed;
905     }
906 
907     if (now - sb.st_mtime < horizon * 60)
908     {
909         Log(LOG_LEVEL_VERBOSE,
910             "Cache file '%s' exists and is sufficiently fresh according to (package_list_update_ifelapsed)", name);
911     }
912     else
913     {
914         Log(LOG_LEVEL_VERBOSE, "Cache file '%s' exists, but it is out of date (package_list_update_ifelapsed)", name);
915         return NULL;
916     }
917 
918     if ((fin = fopen(name, "r")) == NULL)
919     {
920         Log(LOG_LEVEL_ERR, "Cannot open the source log '%s' - you need to run a package discovery promise to create it in cf-agent. (fopen: %s)",
921               name, GetErrorStr());
922         return NULL;
923     }
924 
925 /* Max 2016 entries - at least a week */
926 
927     snprintf(thismanager, CF_MAXVARSIZE - 1, "%s", ReadLastNode(RealPackageManager(manager->manager)));
928 
929     int linenumber = 0;
930     for(;;)
931     {
932         if (fgets(line, sizeof(line), fin) == NULL)
933         {
934             if (ferror(fin))
935             {
936                 UnexpectedError("Failed to read line %d from stream '%s'", linenumber+1, name);
937                 break;
938             }
939             else /* feof */
940             {
941                 break;
942             }
943         }
944         ++linenumber;
945         int scancount = sscanf(line, "%250[^,],%250[^,],%250[^,],%250[^\r\n]", name, version, arch, mgr);
946         if (scancount != 4)
947         {
948             Log(LOG_LEVEL_VERBOSE, "Could only read %d values from line %d in '%s'", scancount, linenumber, name);
949         }
950 
951         /*
952          * Transition to explicit default architecture, if package manager
953          * supports it.
954          *
955          * If old cache contains entries with 'default' architecture, and
956          * package method is updated to detect this architecture, on next
957          * execution update this architecture to the real one.
958          */
959         if (!strcmp(arch, "default"))
960         {
961             strlcpy(arch, default_arch, CF_MAXVARSIZE);
962         }
963 
964         if (strcmp(thismanager, mgr) == 0)
965         {
966             PrependPackageItem(ctx, &list, name, version, arch, pp);
967         }
968     }
969 
970     fclose(fin);
971     return list;
972 }
973 
974 /**
975    @brief Verifies installed packages for a single Promise
976 
977    Called by VerifyPackagesPromise
978 
979    * from all_mgrs, gets the package manager matching a.packages.package_list_command
980    * populate manager->pack_list with GetCachedPackageList
981    * on Windows, use NovaWin_PackageListInstalledFromAPI if a.packages.package_list_command is set to PACKAGE_LIST_COMMAND_WINAPI
982    * on other platforms, use PackageListInstalledFromCommand
983    * call ReportSoftware to save the installed packages inventory
984    * if a.packages.package_patch_list_command is set, use it and parse each line with a.packages.package_patch_installed_regex; if it matches, call PrependPatchItem
985    * call ReportPatches to save the available updates inventory (Enterprise only)
986 
987    @param ctx [in] The evaluation context
988    @param all_mgrs [in] a list of PackageManagers
989    @param default_arch [in] the default architecture
990    @param a [in] the promise Attributes for this operation
991    @param pp [in] the Promise for this operation
992    @param result [inout] the PromiseResult for this operation
993    @returns boolean pass/fail of verification
994 */
VerifyInstalledPackages(EvalContext * ctx,PackageManager ** all_mgrs,const char * default_arch,const Attributes * a,const Promise * pp,PromiseResult * result)995 static bool VerifyInstalledPackages(
996     EvalContext *ctx,
997     PackageManager **all_mgrs,
998     const char *default_arch,
999     const Attributes *a,
1000     const Promise *pp,
1001     PromiseResult *result)
1002 {
1003     assert(a != NULL);
1004 
1005     PackageManager *manager = GetPackageManager(all_mgrs, a->packages.package_list_command, PACKAGE_ACTION_NONE, PACKAGE_ACTION_POLICY_NONE);
1006 
1007     if (manager == NULL)
1008     {
1009         Log(LOG_LEVEL_ERR, "Can't create a package manager envelope for '%s'", a->packages.package_list_command);
1010         return false;
1011     }
1012 
1013     if (manager->pack_list != NULL)
1014     {
1015         Log(LOG_LEVEL_VERBOSE, "Already have a package list for this manager");
1016         return true;
1017     }
1018 
1019     manager->pack_list = GetCachedPackageList(ctx, manager, default_arch, a, pp);
1020 
1021     if (manager->pack_list != NULL)
1022     {
1023         Log(LOG_LEVEL_VERBOSE, "Already have a (cached) package list for this manager ");
1024         return true;
1025     }
1026 
1027     if (a->packages.package_list_command == NULL)
1028     {
1029         /* skip */
1030     }
1031 #ifdef __MINGW32__
1032     else if (strcmp(a->packages.package_list_command, PACKAGE_LIST_COMMAND_WINAPI) == 0)
1033     {
1034         if (!NovaWin_PackageListInstalledFromAPI(ctx, &(manager->pack_list), a, pp))
1035         {
1036             Log(LOG_LEVEL_ERR, "Could not get list of installed packages");
1037             return false;
1038         }
1039     }
1040 #endif /* !__MINGW32__ */
1041     else
1042     {
1043         if (!PackageListInstalledFromCommand(ctx, &(manager->pack_list), default_arch, a, pp, result))
1044         {
1045             Log(LOG_LEVEL_ERR, "Could not get list of installed packages");
1046             return false;
1047         }
1048     }
1049 
1050     ReportSoftware(INSTALLED_PACKAGE_LISTS);
1051 
1052 /* Now get available updates */
1053 
1054     if (a->packages.package_patch_list_command != NULL)
1055     {
1056             Log(LOG_LEVEL_VERBOSE, "Reading patches from '%s'", CommandArg0(a->packages.package_patch_list_command));
1057 
1058         if ((!a->packages.package_commands_useshell) && (!IsExecutable(CommandArg0(a->packages.package_patch_list_command))))
1059         {
1060             Log(LOG_LEVEL_ERR, "The proposed patch list command '%s' was not executable",
1061                   a->packages.package_patch_list_command);
1062             return false;
1063         }
1064 
1065         FILE *fin;
1066 
1067         if (a->packages.package_commands_useshell)
1068         {
1069             if ((fin = cf_popen_sh(a->packages.package_patch_list_command, "r")) == NULL)
1070             {
1071                 Log(LOG_LEVEL_ERR, "Couldn't open the patch list with command '%s'. (cf_popen_sh: %s)",
1072                       a->packages.package_patch_list_command, GetErrorStr());
1073                 return false;
1074             }
1075         }
1076         else if ((fin = cf_popen(a->packages.package_patch_list_command, "r", true)) == NULL)
1077         {
1078             Log(LOG_LEVEL_ERR, "Couldn't open the patch list with command '%s'. (cf_popen: %s)",
1079                   a->packages.package_patch_list_command, GetErrorStr());
1080             return false;
1081         }
1082 
1083         size_t vbuff_size = CF_BUFSIZE;
1084         char *vbuff = xmalloc(vbuff_size);
1085 
1086         for (;;)
1087         {
1088             ssize_t res = CfReadLine(&vbuff, &vbuff_size, fin);
1089             if (res == -1)
1090             {
1091                 if (!feof(fin))
1092                 {
1093                     Log(LOG_LEVEL_ERR, "Unable to read list of patches from command '%s'. (fread: %s)",
1094                           a->packages.package_patch_list_command, GetErrorStr());
1095                     cf_pclose(fin);
1096                     free(vbuff);
1097                     return false;
1098                 }
1099                 else
1100                 {
1101                     break;
1102                 }
1103             }
1104 
1105             // assume patch_list_command lists available patches/updates by default
1106             if ((a->packages.package_patch_installed_regex == NULL)
1107                 || (!FullTextMatch(ctx, a->packages.package_patch_installed_regex, vbuff)))
1108             {
1109                 PrependPatchItem(ctx, &(manager->patch_avail), vbuff, manager->patch_list, default_arch, a, pp);
1110                 continue;
1111             }
1112 
1113             if (!PrependPatchItem(ctx, &(manager->patch_list), vbuff, manager->patch_list, default_arch, a, pp))
1114             {
1115                 continue;
1116             }
1117         }
1118 
1119         cf_pclose(fin);
1120         free(vbuff);
1121     }
1122 
1123     if (a->packages.package_patch_list_command != NULL)
1124     {
1125         ReportPatches(INSTALLED_PACKAGE_LISTS); // Enterprise only
1126     }
1127 
1128         Log(LOG_LEVEL_VERBOSE, "Done checking packages and patches");
1129 
1130     return true;
1131 }
1132 
1133 
1134 /** Evaluate what needs to be done **/
1135 
1136 /**
1137    @brief Finds the largest version of a package available in a file repository
1138 
1139    Called by SchedulePackageOp
1140 
1141    * match = false
1142    * for each directory in repositories
1143    * * try to match refAnyVer against each file
1144    * * if it matches and CompareVersions says it's the biggest found so far, copy the matched version and name into matchName and matchVers and set match to true
1145    * return match
1146 
1147    @param ctx [in] The evaluation context
1148    @param matchName [inout] the matched package name (written on match)
1149    @param matchVers [inout] the matched package version (written on match)
1150    @param refAnyVer [in] the regex to match against the filename to extract a version
1151    @param ver [in] the version sought
1152    @param repositories [in] the list of directories (file repositories)
1153    @param a [in] the promise Attributes for this operation
1154    @param pp [in] the Promise for this operation
1155    @param result [inout] the PromiseResult for this operation
1156    @returns boolean pass/fail of search
1157 */
FindLargestVersionAvail(EvalContext * ctx,char * matchName,char * matchVers,const char * refAnyVer,const char * ver,Rlist * repositories,const Attributes * a,const Promise * pp,PromiseResult * result)1158 int FindLargestVersionAvail(EvalContext *ctx, char *matchName, char *matchVers, const char *refAnyVer, const char *ver,
1159                             Rlist *repositories, const Attributes *a, const Promise *pp, PromiseResult *result)
1160 /* Returns true if a version gt/ge ver is found in local repos, false otherwise */
1161 {
1162     int match = false;
1163 
1164     // match any version
1165     if (!ver[0] || strcmp(ver, "*") == 0)
1166     {
1167         matchVers[0] = '\0';
1168     }
1169     else
1170     {
1171         strlcpy(matchVers, ver, CF_MAXVARSIZE);
1172     }
1173 
1174     for (Rlist *rp = repositories; rp != NULL; rp = rp->next)
1175     {
1176         Dir *dirh = DirOpen(RlistScalarValue(rp));
1177         if (dirh == NULL)
1178         {
1179             Log(LOG_LEVEL_ERR, "Can't open local directory '%s'. (opendir: %s)",
1180                 RlistScalarValue(rp), GetErrorStr());
1181             continue;
1182         }
1183 
1184         const struct dirent *dirp;
1185         while ((dirp = DirRead(dirh)) != NULL)
1186         {
1187             if (FullTextMatch(ctx, refAnyVer, dirp->d_name))
1188             {
1189                 char *matchVer = ExtractFirstReference(refAnyVer, dirp->d_name);
1190 
1191                 // check if match is largest so far
1192                 if (CompareVersions(ctx, matchVer, matchVers, a, pp, result) == VERCMP_MATCH)
1193                 {
1194                     strlcpy(matchVers, matchVer, CF_MAXVARSIZE);
1195                     strlcpy(matchName, dirp->d_name, CF_MAXVARSIZE);
1196                     match = true;
1197                 }
1198             }
1199         }
1200 
1201         DirClose(dirh);
1202     }
1203 
1204     Log(LOG_LEVEL_DEBUG, "FindLargestVersionAvail: largest version of '%s' is '%s' (match=%d)",
1205         matchName, matchVers, match);
1206 
1207     return match;
1208 }
1209 
1210 /**
1211    @brief Returns true if a package (n,v,a) is installed and v is larger than the installed version
1212 
1213    Called by SchedulePackageOp
1214 
1215    * for each known PackageManager, compare to attr.packages.package_list_command
1216    * bail out if no manager was found
1217    * for each PackageItem pi in the manager's package list
1218    * * if pi->name equals n and (a is "*" or a equals pi->arch)
1219    * * * record instV and instA
1220    * * * copy attr into attr2 and override the attr2.packages.package_select to PACKAGE_VERSION_COMPARATOR_LT
1221    * * * return CompareVersions of the new monster
1222    * return false if the above found no matches
1223 
1224    @param ctx [in] The evaluation context
1225    @param n [in] the specific name
1226    @param v [in] the specific version
1227    @param a [in] the specific architecture
1228    @param instV [inout] the matched package version (written on match)
1229    @param instA [inout] the matched package architecture (written on match)
1230    @param attr [in] the promise Attributes for this operation
1231    @param pp [in] the Promise for this operation
1232    @param result [inout] the PromiseResult for this operation
1233    @returns boolean if given (n,v,a) is newer than known packages
1234 */
IsNewerThanInstalled(EvalContext * ctx,const char * n,const char * v,const char * a,char * instV,char * instA,const Attributes * attr,const Promise * pp,PromiseResult * result)1235 static bool IsNewerThanInstalled(
1236     EvalContext *ctx,
1237     const char *n,
1238     const char *v,
1239     const char *a,
1240     char *instV,
1241     char *instA,
1242     const Attributes *attr,
1243     const Promise *pp,
1244     PromiseResult *result)
1245 {
1246     assert(attr != NULL);
1247     PackageManager *mp = INSTALLED_PACKAGE_LISTS;
1248     while (mp != NULL)
1249     {
1250         if (strcmp(mp->manager, attr->packages.package_list_command) == 0)
1251         {
1252             break;
1253         }
1254         mp = mp->next;
1255     }
1256 
1257     if (mp == NULL)
1258     {
1259         Log(LOG_LEVEL_VERBOSE, "Found no package manager matching attr.packages.package_list_command '%s'",
1260             attr->packages.package_list_command == NULL ? "[empty]" : attr->packages.package_list_command);
1261         return false;
1262     }
1263 
1264     Log(LOG_LEVEL_VERBOSE, "Looking for an installed package older than (%s,%s,%s) [name,version,arch]", n, v, a);
1265 
1266     for (PackageItem *pi = mp->pack_list; pi != NULL; pi = pi->next)
1267     {
1268         if (strcmp(n, pi->name) == 0 &&
1269             (strcmp(a, "*") == 0 || strcmp(a, pi->arch) == 0))
1270         {
1271             Log(LOG_LEVEL_VERBOSE,
1272                 "Found installed package (%s,%s,%s) [name,version,arch]",
1273                 pi->name, pi->version, pi->arch);
1274 
1275             strlcpy(instV, pi->version, CF_MAXVARSIZE);
1276             strlcpy(instA, pi->arch, CF_MAXVARSIZE);
1277 
1278             /* Horrible */
1279             Attributes attr2 = *attr;
1280             attr2.packages.package_select = PACKAGE_VERSION_COMPARATOR_LT;
1281 
1282             return CompareVersions(ctx, pi->version, v, &attr2, pp, result) == VERCMP_MATCH;
1283         }
1284     }
1285 
1286     Log(LOG_LEVEL_VERBOSE, "Package (%s,%s) [name,arch] is not installed", n, a);
1287     return false;
1288 }
1289 
1290 /**
1291    @brief Returns string version of a PackageAction
1292 
1293    @param pa [in] The PackageAction
1294    @returns string representation of pa or a ProgrammingError
1295 */
PackageAction2String(PackageAction pa)1296 static const char *PackageAction2String(PackageAction pa)
1297 {
1298     switch (pa)
1299     {
1300     case PACKAGE_ACTION_ADD:
1301         return "installing";
1302     case PACKAGE_ACTION_DELETE:
1303         return "uninstalling";
1304     case PACKAGE_ACTION_REINSTALL:
1305         return "reinstalling";
1306     case PACKAGE_ACTION_UPDATE:
1307         return "updating";
1308     case PACKAGE_ACTION_ADDUPDATE:
1309         return "installing/updating";
1310     case PACKAGE_ACTION_PATCH:
1311         return "patching";
1312     case PACKAGE_ACTION_VERIFY:
1313         return "verifying";
1314     default:
1315         ProgrammingError("CFEngine: internal error: illegal package action");
1316     }
1317 }
1318 
1319 /**
1320    @brief Adds a specific package (name,version,arch) as specified by Attributes a to the scheduled operations
1321 
1322    Called by SchedulePackageOp.
1323 
1324    Either warn or fix, based on a->transaction.action.
1325 
1326    To fix, calls GetPackageManager and enqueues the desired operation and package with the returned manager
1327 
1328    @param ctx [in] The evaluation context
1329    @param a [in] the Attributes specifying how to compare
1330    @param mgr [in] the specific manager name
1331    @param pa [in] the PackageAction to enqueue
1332    @param name [in] the specific name
1333    @param version [in] the specific version
1334    @param arch [in] the specific architecture
1335    @param pp [in] the Promise for this operation
1336    @returns the promise result
1337 */
AddPackageToSchedule(EvalContext * ctx,const Attributes * a,char * mgr,PackageAction pa,const char * name,const char * version,const char * arch,const Promise * pp)1338 static PromiseResult AddPackageToSchedule(EvalContext *ctx, const Attributes *a, char *mgr, PackageAction pa,
1339                                           const char *name, const char *version, const char *arch,
1340                                           const Promise *pp)
1341 {
1342     assert(a != NULL);
1343     assert(pp != NULL);
1344 
1345     switch (a->transaction.action)
1346     {
1347     case cfa_warn:
1348 
1349         cfPS_HELPER_3ARG(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, a, "Need to repair promise '%s' by '%s' package '%s'",
1350              pp->promiser, PackageAction2String(pa), name);
1351         return PROMISE_RESULT_WARN;
1352 
1353     case cfa_fix:
1354     {
1355         PackageManager *manager = GetPackageManager(&PACKAGE_SCHEDULE, mgr, pa, a->packages.package_changes);
1356 
1357         if (manager == NULL)
1358         {
1359             ProgrammingError("AddPackageToSchedule: Null package manager found!!!");
1360         }
1361 
1362         PrependPackageItem(ctx, &(manager->pack_list), name, version, arch, pp);
1363         return PROMISE_RESULT_CHANGE;
1364     }
1365     default:
1366         ProgrammingError("CFEngine: internal error: illegal file action");
1367     }
1368 }
1369 
1370 /**
1371    @brief Adds a specific patch (name,version,arch) as specified by Attributes a to the scheduled operations
1372 
1373    Called by SchedulePackageOp.
1374 
1375    Either warn or fix, based on a->transaction.action.
1376 
1377    To fix, calls GetPackageManager and enqueues the desired operation and package with the returned manager
1378 
1379    @param ctx [in] The evaluation context
1380    @param a [in] the Attributes specifying how to compare
1381    @param mgr [in] the specific manager name
1382    @param pa [in] the PackageAction to enqueue
1383    @param name [in] the specific name
1384    @param version [in] the specific version
1385    @param arch [in] the specific architecture
1386    @param pp [in] the Promise for this operation
1387    @returns the promise result
1388 */
AddPatchToSchedule(EvalContext * ctx,const Attributes * a,char * mgr,PackageAction pa,const char * name,const char * version,const char * arch,const Promise * pp)1389 static PromiseResult AddPatchToSchedule(EvalContext *ctx, const Attributes *a, char *mgr, PackageAction pa,
1390                                         const char *name, const char *version, const char *arch,
1391                                         const Promise *pp)
1392 {
1393     assert(a != NULL);
1394     assert(pp != NULL);
1395 
1396     switch (a->transaction.action)
1397     {
1398     case cfa_warn:
1399 
1400         cfPS_HELPER_3ARG(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, a, "Need to repair promise '%s' by '%s' package '%s'",
1401              pp->promiser, PackageAction2String(pa), name);
1402         return PROMISE_RESULT_WARN;
1403 
1404     case cfa_fix:
1405     {
1406         PackageManager *manager = GetPackageManager(&PACKAGE_SCHEDULE, mgr, pa, a->packages.package_changes);
1407 
1408         if (manager == NULL)
1409         {
1410             ProgrammingError("AddPatchToSchedule: Null package manager found!!!");
1411         }
1412 
1413         PrependPackageItem(ctx, &(manager->patch_list), name, version, arch, pp);
1414         return PROMISE_RESULT_CHANGE;
1415     }
1416     default:
1417         ProgrammingError("Illegal file action");
1418     }
1419 }
1420 
1421 /**
1422    @brief Schedules a package operation based on the action, package state, and everything else.
1423 
1424    Called by VerifyPromisedPatch and CheckPackageState.
1425 
1426    This function has a complexity metric of 3 Googols.
1427 
1428    * if package_delete_convention or package_name_convention are given and apply to the operation, construct the package name from them (from PACKAGES_CONTEXT)
1429    * else, just use the given package name
1430    * warn about "*" in the package name
1431    * set package_select_in_range with magic
1432    * create PackageAction policy from the package_policy and then split ADDUPDATE into ADD or UPDATE based on "installed"
1433    * result starts as NOOP
1434    * switch(policy)
1435 
1436    * * case ADD and "installed":
1437    * * * if we have package_file_repositories
1438    * * * * use the package_name_convention to build the package name (from PACKAGES_CONTEXT_ANYVER, setting version to "*")
1439    * * * * if FindLargestVersionAvail finds the latest package version in the file repos, use that as the package name
1440    * * * AddPackageToSchedule package_add_command, ADD, package name, etc.
1441 
1442    * * case DELETE and (matched AND package_select_in_range) OR (installed AND no_version_specified):
1443    * * * fail promise unless package_delete_command
1444    * * * if we have package_file_repositories
1445    * * * * clean up the name string from any "repo" references and add the right file repo
1446    * * * AddPackageToSchedule package_delete_command, DELETE, package name, etc.
1447 
1448    * * case REINSTALL:
1449    * * * fail promise unless package_delete_command
1450    * * * fail promise if no_version_specified
1451    * * * if (matched AND package_select_in_range) OR (installed AND no_version_specified) do AddPackageToSchedule package_delete_command, DELETE, package name, etc.
1452    * * * AddPackageToSchedule package_add_command, ADD, package name, etc.
1453 
1454    * * case UPDATE:
1455    * * * if we have package_file_repositories
1456    * * * * use the package_name_convention to build the package name (from PACKAGES_CONTEXT_ANYVER, setting version to "*")
1457    * * * * if FindLargestVersionAvail finds the latest package version in the file repos, use that as the package name
1458    * * * if installed, IsNewerThanInstalled is checked, and if it returns false we don't update an up-to-date package
1459    * * * if installed or (matched AND package_select_in_range AND !no_version_specified) (this is the main update condition)
1460    * * * * if package_update_command is not given
1461    * * * * * if package_delete_convention is given, use it to build id_del (from PACKAGES_CONTEXT)
1462    * * * * * fail promise if package_update_command and package_add_command are not given
1463    * * * * * AddPackageToSchedule with package_delete_command, DELETE, id_del, etc
1464    * * * * * AddPackageToSchedule with package_add_command, ADD, package name, etc
1465    * * * * else we have package_update_command, so AddPackageToSchedule with package_update_command, UPDATE, package name, etc
1466    * * * else the package is not updateable: no match or not installed, fail promise
1467 
1468    * * case PATCH:
1469    * * * if matched and not installed, AddPatchToSchedule with package_patch_command, PATCH, package name, etc.
1470 
1471    * * case VERIFY:
1472    * * * if (matched and package_select_in_range) OR (installed AND no_version_specified), AddPatchToSchedule with package_verify_command, VERIFY, package name, etc.
1473 
1474    @param ctx [in] The evaluation context
1475    @param name [in] the specific name
1476    @param version [in] the specific version
1477    @param arch [in] the specific architecture
1478    @param installed [in] is the package installed?
1479    @param matched [in] is the package matched in the available list?
1480    @param no_version_specified [in] no version was specified in the promise
1481    @param a [in] the Attributes specifying how to compare
1482    @param pp [in] the Promise for this operation
1483    @returns the promise result
1484 */
SchedulePackageOp(EvalContext * ctx,const char * name,const char * version,const char * arch,int installed,int matched,int no_version_specified,const Attributes * a,const Promise * pp)1485 static PromiseResult SchedulePackageOp(EvalContext *ctx, const char *name, const char *version, const char *arch, int installed, int matched,
1486                                        int no_version_specified, const Attributes *a, const Promise *pp)
1487 {
1488     assert(a != NULL);
1489     assert(pp != NULL); // Dereferenced by cfPS macros
1490 
1491     char refAnyVerEsc[CF_EXPANDSIZE];
1492     char largestVerAvail[CF_MAXVARSIZE];
1493     char largestPackAvail[CF_MAXVARSIZE];
1494     char id[CF_EXPANDSIZE];
1495 
1496     Log(LOG_LEVEL_VERBOSE,
1497         "Checking if package (%s,%s,%s) [name,version,arch] "
1498         "is at the desired state (installed=%d,matched=%d)",
1499         name, version, arch, installed, matched);
1500 
1501 /* Now we need to know the name-convention expected by the package manager */
1502 
1503     Buffer *expanded = BufferNew();
1504     if ((a->packages.package_name_convention) || (a->packages.package_delete_convention))
1505     {
1506         VarRef *ref_name = VarRefParseFromScope("name", PACKAGES_CONTEXT);
1507         EvalContextVariablePut(ctx, ref_name, name, CF_DATA_TYPE_STRING, "source=promise");
1508 
1509         VarRef *ref_version = VarRefParseFromScope("version", PACKAGES_CONTEXT);
1510         EvalContextVariablePut(ctx, ref_version, version, CF_DATA_TYPE_STRING, "source=promise");
1511 
1512         VarRef *ref_arch = VarRefParseFromScope("arch", PACKAGES_CONTEXT);
1513         EvalContextVariablePut(ctx, ref_arch, arch, CF_DATA_TYPE_STRING, "source=promise");
1514 
1515         if ((a->packages.package_delete_convention) && (a->packages.package_policy == PACKAGE_ACTION_DELETE))
1516         {
1517             ExpandScalar(ctx, NULL, PACKAGES_CONTEXT, a->packages.package_delete_convention, expanded);
1518             strlcpy(id, BufferData(expanded), CF_EXPANDSIZE);
1519         }
1520         else if (a->packages.package_name_convention)
1521         {
1522             ExpandScalar(ctx, NULL, PACKAGES_CONTEXT, a->packages.package_name_convention, expanded);
1523             strlcpy(id, BufferData(expanded), CF_EXPANDSIZE);
1524         }
1525         else
1526         {
1527             strlcpy(id, name, CF_EXPANDSIZE);
1528         }
1529 
1530         EvalContextVariableRemove(ctx, ref_name);
1531         VarRefDestroy(ref_name);
1532 
1533         EvalContextVariableRemove(ctx, ref_version);
1534         VarRefDestroy(ref_version);
1535 
1536         EvalContextVariableRemove(ctx, ref_arch);
1537         VarRefDestroy(ref_arch);
1538     }
1539     else
1540     {
1541         strlcpy(id, name, CF_EXPANDSIZE);
1542     }
1543 
1544     Log(LOG_LEVEL_VERBOSE, "Package promises to refer to itself as '%s' to the manager", id);
1545 
1546     if (strchr(id, '*'))
1547     {
1548         Log(LOG_LEVEL_VERBOSE, "Package name contains '*' -- perhaps "
1549             "a missing attribute (name/version/arch) should be specified");
1550     }
1551 
1552     // This is very confusing
1553     int package_select_in_range;
1554     switch (a->packages.package_select)
1555     {
1556     case PACKAGE_VERSION_COMPARATOR_EQ:
1557     case PACKAGE_VERSION_COMPARATOR_GE:
1558     case PACKAGE_VERSION_COMPARATOR_LE:
1559     case PACKAGE_VERSION_COMPARATOR_NONE:
1560         Log(LOG_LEVEL_VERBOSE, "Package version seems to match criteria");
1561         package_select_in_range = true;
1562         break;
1563 
1564     default:
1565         package_select_in_range = false;
1566         break;
1567     }
1568 
1569     PackageAction policy = a->packages.package_policy;
1570     if (policy == PACKAGE_ACTION_ADDUPDATE) /* Work out which: */
1571     {
1572         if (installed)
1573         {
1574             policy = PACKAGE_ACTION_UPDATE;
1575         }
1576         else
1577         {
1578             policy = PACKAGE_ACTION_ADD;
1579         }
1580     }
1581 
1582     PromiseResult result = PROMISE_RESULT_NOOP;
1583     switch (policy)
1584     {
1585     case PACKAGE_ACTION_ADD:
1586 
1587         if (installed == 0)
1588         {
1589             if ((a->packages.package_file_repositories != NULL))
1590             {
1591                 Log(LOG_LEVEL_VERBOSE, "Package method specifies a file repository");
1592 
1593                 {
1594                     VarRef *ref_name = VarRefParseFromScope("name", PACKAGES_CONTEXT_ANYVER);
1595                     EvalContextVariablePut(ctx, ref_name, name, CF_DATA_TYPE_STRING, "source=promise");
1596 
1597                     VarRef *ref_version = VarRefParseFromScope("version", PACKAGES_CONTEXT_ANYVER);
1598                     EvalContextVariablePut(ctx, ref_version, "(.*)", CF_DATA_TYPE_STRING, "source=promise");
1599 
1600                     VarRef *ref_arch = VarRefParseFromScope("arch", PACKAGES_CONTEXT_ANYVER);
1601                     EvalContextVariablePut(ctx, ref_arch, arch, CF_DATA_TYPE_STRING, "source=promise");
1602 
1603                     BufferClear(expanded);
1604                     if (a->packages.package_name_convention)
1605                     {
1606                         ExpandScalar(ctx, NULL, PACKAGES_CONTEXT_ANYVER, a->packages.package_name_convention, expanded);
1607                     }
1608 
1609                     EvalContextVariableRemove(ctx, ref_name);
1610                     VarRefDestroy(ref_name);
1611 
1612                     EvalContextVariableRemove(ctx, ref_version);
1613                     VarRefDestroy(ref_version);
1614 
1615                     EvalContextVariableRemove(ctx, ref_arch);
1616                     VarRefDestroy(ref_arch);
1617                 }
1618 
1619                 EscapeSpecialChars(BufferData(expanded), refAnyVerEsc, sizeof(refAnyVerEsc), "(.*)","");
1620 
1621                 if (FindLargestVersionAvail(ctx, largestPackAvail, largestVerAvail, refAnyVerEsc, version,
1622                                             a->packages.package_file_repositories, a, pp, &result))
1623                 {
1624                     Log(LOG_LEVEL_VERBOSE, "Using latest version in file repositories; '%s'", largestPackAvail);
1625                     strlcpy(id, largestPackAvail, CF_EXPANDSIZE);
1626                 }
1627                 else
1628                 {
1629                     Log(LOG_LEVEL_VERBOSE, "No package in file repositories satisfy version constraint");
1630                     break;
1631                 }
1632             }
1633             else
1634             {
1635                 Log(LOG_LEVEL_VERBOSE, "Package method does NOT specify a file repository");
1636             }
1637 
1638             Log(LOG_LEVEL_VERBOSE, "Schedule package for addition");
1639 
1640             if (a->packages.package_add_command == NULL)
1641             {
1642                 cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Package add command undefined");
1643                 BufferDestroy(expanded);
1644                 return PROMISE_RESULT_FAIL;
1645             }
1646             result = PromiseResultUpdate_HELPER(pp, result,
1647                                          AddPackageToSchedule(ctx, a, a->packages.package_add_command,
1648                                                               PACKAGE_ACTION_ADD, id, "any", "any", pp));
1649         }
1650         else
1651         {
1652             cfPS_HELPER_1ARG(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "Package '%s' already installed, so we never add it again",
1653                  pp->promiser);
1654         }
1655         break;
1656 
1657     case PACKAGE_ACTION_DELETE:
1658 
1659         // we're deleting a matched package found in a range OR an installed package with no version
1660         if ((matched && package_select_in_range) ||
1661             (installed && no_version_specified))
1662         {
1663             Log(LOG_LEVEL_VERBOSE, "Schedule package for deletion");
1664 
1665             if (a->packages.package_delete_command == NULL)
1666             {
1667                 cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Package delete command undefined");
1668                 BufferDestroy(expanded);
1669                 return PROMISE_RESULT_FAIL;
1670             }
1671             // expand local repository in the name convention, if present
1672             if (a->packages.package_file_repositories)
1673             {
1674                 Log(LOG_LEVEL_VERBOSE, "Package method specifies a file repository");
1675 
1676                 // remove any "$(repo)" from the name convention string
1677 
1678                 if (strncmp(id, "$(firstrepo)", 12) == 0)
1679                 {
1680                     const char *idBuf = id + 12;
1681 
1682                     // and add the correct repo
1683                     const char *pathName = PrefixLocalRepository(a->packages.package_file_repositories, idBuf);
1684 
1685                     if (pathName)
1686                     {
1687                         strlcpy(id, pathName, CF_EXPANDSIZE);
1688                         Log(LOG_LEVEL_VERBOSE,
1689                             "Expanded the package repository to '%s'", id);
1690                     }
1691                     else
1692                     {
1693                         Log(LOG_LEVEL_ERR, "Package '%s' can't be found "
1694                             "in any of the listed repositories", idBuf);
1695                     }
1696                 }
1697             }
1698             else
1699             {
1700                 Log(LOG_LEVEL_VERBOSE, "Package method does NOT specify a file repository");
1701             }
1702 
1703             result = PromiseResultUpdate_HELPER(pp, result,
1704                                          AddPackageToSchedule(ctx, a, a->packages.package_delete_command,
1705                                                               PACKAGE_ACTION_DELETE, id, "any", "any", pp));
1706         }
1707         else
1708         {
1709             cfPS_HELPER_0ARG(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "Package deletion is as promised -- no match");
1710         }
1711         break;
1712 
1713     case PACKAGE_ACTION_REINSTALL:
1714         if (a->packages.package_delete_command == NULL)
1715         {
1716             cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Package delete command undefined");
1717             BufferDestroy(expanded);
1718             return PROMISE_RESULT_FAIL;
1719         }
1720 
1721         if (!no_version_specified)
1722         {
1723             Log(LOG_LEVEL_VERBOSE, "Schedule package for reinstallation");
1724             if (a->packages.package_add_command == NULL)
1725             {
1726                 cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Package add command undefined");
1727                 BufferDestroy(expanded);
1728                 return PROMISE_RESULT_FAIL;
1729             }
1730 
1731             // we're deleting a matched package found in a range OR an installed package with no version
1732             if ((matched && package_select_in_range) ||
1733                 (installed && no_version_specified))
1734             {
1735                 result = PromiseResultUpdate_HELPER(pp, result,
1736                                              AddPackageToSchedule(ctx, a, a->packages.package_delete_command,
1737                                                                   PACKAGE_ACTION_DELETE, id, "any", "any", pp));
1738             }
1739 
1740             result = PromiseResultUpdate_HELPER(pp, result,
1741                                          AddPackageToSchedule(ctx, a, a->packages.package_add_command,
1742                                                               PACKAGE_ACTION_ADD, id, "any", "any", pp));
1743         }
1744         else
1745         {
1746             cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
1747                  "Package reinstallation cannot be promised -- insufficient version info or no match");
1748             BufferDestroy(expanded);
1749             return PROMISE_RESULT_FAIL;
1750         }
1751         break;
1752 
1753     case PACKAGE_ACTION_UPDATE:
1754     {
1755         char inst_arch[CF_MAXVARSIZE];
1756         char inst_ver[CF_MAXVARSIZE];
1757         *inst_ver = '\0';
1758         *inst_arch = '\0';
1759 
1760         if ((a->packages.package_file_repositories != NULL))
1761         {
1762             Log(LOG_LEVEL_VERBOSE, "Package method specifies a file repository");
1763 
1764             {
1765                 VarRef *ref_name = VarRefParseFromScope("name", PACKAGES_CONTEXT_ANYVER);
1766                 EvalContextVariablePut(ctx, ref_name, name, CF_DATA_TYPE_STRING, "source=promise");
1767 
1768                 VarRef *ref_version = VarRefParseFromScope("version", PACKAGES_CONTEXT_ANYVER);
1769                 EvalContextVariablePut(ctx, ref_version, "(.*)", CF_DATA_TYPE_STRING, "source=promise");
1770 
1771                 VarRef *ref_arch = VarRefParseFromScope("arch", PACKAGES_CONTEXT_ANYVER);
1772                 EvalContextVariablePut(ctx, ref_arch, arch, CF_DATA_TYPE_STRING, "source=promise");
1773 
1774                 BufferClear(expanded);
1775                 ExpandScalar(ctx, NULL, PACKAGES_CONTEXT_ANYVER, a->packages.package_name_convention, expanded);
1776 
1777                 EvalContextVariableRemove(ctx, ref_name);
1778                 VarRefDestroy(ref_name);
1779 
1780                 EvalContextVariableRemove(ctx, ref_version);
1781                 VarRefDestroy(ref_version);
1782 
1783                 EvalContextVariableRemove(ctx, ref_arch);
1784                 VarRefDestroy(ref_arch);
1785             }
1786 
1787 
1788             EscapeSpecialChars(BufferData(expanded), refAnyVerEsc, sizeof(refAnyVerEsc), "(.*)","");
1789 
1790             if (FindLargestVersionAvail(ctx, largestPackAvail, largestVerAvail, refAnyVerEsc, version,
1791                                         a->packages.package_file_repositories, a, pp, &result))
1792             {
1793                 Log(LOG_LEVEL_VERBOSE, "Using latest version in file repositories; '%s'", largestPackAvail);
1794                 strlcpy(id, largestPackAvail, CF_EXPANDSIZE);
1795             }
1796             else
1797             {
1798                 Log(LOG_LEVEL_VERBOSE, "No package in file repositories satisfy version constraint");
1799                 break;
1800             }
1801         }
1802         else
1803         {
1804             Log(LOG_LEVEL_VERBOSE, "Package method does NOT specify a file repository");
1805             strlcpy(largestVerAvail, version, sizeof(largestVerAvail));  // user-supplied version
1806         }
1807 
1808         if (installed)
1809         {
1810             Log(LOG_LEVEL_VERBOSE, "Checking if latest available version is newer than installed...");
1811             if (IsNewerThanInstalled(ctx, name, largestVerAvail, arch, inst_ver, inst_arch, a, pp, &result))
1812             {
1813                 Log(LOG_LEVEL_VERBOSE,
1814                       "Installed package (%s,%s,%s) [name,version,arch] is older than latest available (%s,%s,%s) [name,version,arch] - updating", name,
1815                       inst_ver, inst_arch, name, largestVerAvail, arch);
1816             }
1817             else
1818             {
1819                 cfPS_HELPER_1ARG(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a,
1820                     "Installed packaged '%s' is up to date, not updating", pp->promiser);
1821                 break;
1822             }
1823         }
1824 
1825         if (installed ||
1826             (matched && package_select_in_range && !no_version_specified))
1827         {
1828             if (a->packages.package_update_command == NULL)
1829             {
1830                 Log(LOG_LEVEL_VERBOSE, "Package update command undefined - failing over to delete then add");
1831 
1832                 // we need to have the version of installed package
1833                 const char *id_del = id;
1834                 if (a->packages.package_delete_convention)
1835                 {
1836                     if (*inst_ver == '\0')
1837                     {
1838                         inst_ver[0] = '*';
1839                         inst_ver[1] = '\0';
1840                     }
1841 
1842                     if (*inst_arch == '\0')
1843                     {
1844                         inst_arch[0] = '*';
1845                         inst_arch[1] = '\0';
1846                     }
1847 
1848                     VarRef *ref_name = VarRefParseFromScope("name", PACKAGES_CONTEXT);
1849                     EvalContextVariablePut(ctx, ref_name, name, CF_DATA_TYPE_STRING, "source=promise");
1850 
1851                     VarRef *ref_version = VarRefParseFromScope("version", PACKAGES_CONTEXT);
1852                     EvalContextVariablePut(ctx, ref_version, inst_ver, CF_DATA_TYPE_STRING, "source=promise");
1853 
1854                     VarRef *ref_arch = VarRefParseFromScope("arch", PACKAGES_CONTEXT);
1855                     EvalContextVariablePut(ctx, ref_arch, inst_arch, CF_DATA_TYPE_STRING, "source=promise");
1856 
1857                     BufferClear(expanded);
1858                     ExpandScalar(ctx, NULL, PACKAGES_CONTEXT, a->packages.package_delete_convention, expanded);
1859                     id_del = BufferData(expanded);
1860 
1861                     EvalContextVariableRemove(ctx, ref_name);
1862                     VarRefDestroy(ref_name);
1863 
1864                     EvalContextVariableRemove(ctx, ref_version);
1865                     VarRefDestroy(ref_version);
1866 
1867                     EvalContextVariableRemove(ctx, ref_arch);
1868                     VarRefDestroy(ref_arch);
1869                 }
1870 
1871                 Log(LOG_LEVEL_VERBOSE, "Scheduling package with id '%s' for deletion", id_del);
1872 
1873                 if (a->packages.package_add_command == NULL)
1874                 {
1875                     cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Package add command undefined");
1876                     BufferDestroy(expanded);
1877                     return PROMISE_RESULT_FAIL;
1878                 }
1879                 if (a->packages.package_delete_command == NULL)
1880                 {
1881                     cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Package delete command undefined");
1882                     BufferDestroy(expanded);
1883                     return PROMISE_RESULT_FAIL;
1884                 }
1885                 result = PromiseResultUpdate_HELPER(pp, result,
1886                                              AddPackageToSchedule(ctx, a, a->packages.package_delete_command,
1887                                                                   PACKAGE_ACTION_DELETE, id_del, "any", "any", pp));
1888 
1889                 result = PromiseResultUpdate_HELPER(pp, result,
1890                                              AddPackageToSchedule(ctx, a, a->packages.package_add_command,
1891                                                                   PACKAGE_ACTION_ADD, id, "any", "any", pp));
1892             }
1893             else
1894             {
1895                 Log(LOG_LEVEL_VERBOSE, "Schedule package for update");
1896                 result = PromiseResultUpdate_HELPER(pp, result,
1897                                              AddPackageToSchedule(ctx, a, a->packages.package_update_command,
1898                                                                   PACKAGE_ACTION_UPDATE, id, "any", "any", pp));
1899             }
1900         }
1901         else
1902         {
1903             cfPS_HELPER_1ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Package '%s' cannot be updated -- no match or not installed",
1904                  pp->promiser);
1905             result = PromiseResultUpdate_HELPER(pp, result, PROMISE_RESULT_FAIL);
1906         }
1907         break;
1908     }
1909     case PACKAGE_ACTION_PATCH:
1910 
1911         if (matched && (!installed))
1912         {
1913             Log(LOG_LEVEL_VERBOSE, "Schedule package for patching");
1914             result = PromiseResultUpdate_HELPER(pp, result,
1915                                          AddPatchToSchedule(ctx, a, a->packages.package_patch_command,
1916                                                             PACKAGE_ACTION_PATCH, id, "any", "any", pp));
1917         }
1918         else
1919         {
1920             cfPS_HELPER_1ARG(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a,
1921                  "Package patch state of '%s' is as promised -- already installed", pp->promiser);
1922         }
1923         break;
1924 
1925     case PACKAGE_ACTION_VERIFY:
1926 
1927         if ((matched && package_select_in_range) ||
1928             (installed && no_version_specified))
1929         {
1930             Log(LOG_LEVEL_VERBOSE, "Schedule package for verification");
1931             result = PromiseResultUpdate_HELPER(pp, result,
1932                                          AddPackageToSchedule(ctx, a, a->packages.package_verify_command,
1933                                                               PACKAGE_ACTION_VERIFY, id, "any", "any", pp));
1934         }
1935         else
1936         {
1937             cfPS_HELPER_1ARG(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_FAIL, pp, a, "Package '%s' cannot be verified -- no match", pp->promiser);
1938             BufferDestroy(expanded);
1939             return PROMISE_RESULT_FAIL;
1940         }
1941 
1942         break;
1943 
1944     default:
1945         break;
1946     }
1947 
1948     BufferDestroy(expanded);
1949     return result;
1950 }
1951 
1952 /**
1953    @brief Compare a PackageItem to a specific package (n,v,arch) as specified by Attributes a
1954 
1955    Called by PatchMatch and PackageMatch.
1956 
1957    First, checks the package names are the same (according to CompareCSVName).
1958    Second, checks the architectures are the same or arch is "*"
1959    Third, checks the versions with CompareVersions or version is "*"
1960 
1961    @param ctx [in] The evaluation context
1962    @param n [in] the specific name
1963    @param v [in] the specific version
1964    @param arch [in] the specific architecture
1965    @param pi [in] the PackageItem to check
1966    @param a [in] the Attributes specifying how to compare
1967    @param pp [in] the Promise for this operation
1968    @param mode [in] the operating mode, informational for logging
1969    @returns the version comparison result
1970 */
ComparePackages(EvalContext * ctx,const char * n,const char * v,const char * arch,PackageItem * pi,const Attributes * a,const Promise * pp,const char * mode,PromiseResult * result)1971 VersionCmpResult ComparePackages(EvalContext *ctx,
1972                                  const char *n, const char *v, const char *arch,
1973                                  PackageItem *pi, const Attributes *a,
1974                                  const Promise *pp,
1975                                  const char *mode,
1976                                  PromiseResult *result)
1977 {
1978     assert(pi != NULL);
1979     assert(a != NULL);
1980 
1981     Log(LOG_LEVEL_VERBOSE, "Comparing %s package (%s,%s,%s) "
1982         "to [%s] with given (%s,%s,%s) [name,version,arch]",
1983         mode, pi->name, pi->version, pi->arch, PackageVersionComparatorToString(a->packages.package_select), n, v, arch);
1984 
1985     if (CompareCSVName(n, pi->name) != 0)
1986     {
1987         return VERCMP_NO_MATCH;
1988     }
1989 
1990     Log(LOG_LEVEL_VERBOSE, "Matched %s name '%s'", mode, n);
1991 
1992     if (strcmp(arch, "*") != 0)
1993     {
1994         if (strcmp(arch, pi->arch) != 0)
1995         {
1996             return VERCMP_NO_MATCH;
1997         }
1998 
1999         Log(LOG_LEVEL_VERBOSE, "Matched %s arch '%s'", mode, arch);
2000     }
2001     else
2002     {
2003         Log(LOG_LEVEL_VERBOSE, "Matched %s wildcard arch '%s'", mode, arch);
2004     }
2005 
2006     if (strcmp(v, "*") == 0)
2007     {
2008         Log(LOG_LEVEL_VERBOSE, "Matched %s wildcard version '%s'", mode, v);
2009         return VERCMP_MATCH;
2010     }
2011 
2012     VersionCmpResult vc = CompareVersions(ctx, pi->version, v, a, pp, result);
2013     Log(LOG_LEVEL_VERBOSE,
2014         "Version comparison returned %s for %s package (%s,%s,%s) "
2015         "to [%s] with given (%s,%s,%s) [name,version,arch]",
2016         vc == VERCMP_MATCH ? "MATCH" : vc == VERCMP_NO_MATCH ? "NO_MATCH" : "ERROR",
2017         mode,
2018         pi->name, pi->version, pi->arch,
2019         PackageVersionComparatorToString(a->packages.package_select),
2020         n, v, arch);
2021 
2022     return vc;
2023 
2024 }
2025 
2026 /**
2027    @brief Finds a specific package (n,v,a) [name, version, architecture] as specified by Attributes attr
2028 
2029    Called by VerifyPromisedPatch.
2030 
2031    Goes through all the installed packages to find matches for the given attributes.
2032 
2033    The package manager is checked against attr.packages.package_list_command.
2034 
2035    The package name is checked as a regular expression. then (n,v,a) with ComparePackages.
2036 
2037    @param ctx [in] The evaluation context
2038    @param n [in] the specific name
2039    @param v [in] the specific version
2040    @param a [in] the specific architecture
2041    @param attr [in] the Attributes specifying how to compare
2042    @param pp [in] the Promise for this operation
2043    @param mode [in] the operating mode, informational for logging
2044    @returns the version comparison result
2045 */
PatchMatch(EvalContext * ctx,const char * n,const char * v,const char * a,const Attributes * attr,const Promise * pp,const char * mode,PromiseResult * result)2046 static VersionCmpResult PatchMatch(EvalContext *ctx,
2047                                    const char *n, const char *v, const char *a,
2048                                    const Attributes *attr, const Promise *pp,
2049                                    const char* mode,
2050                                    PromiseResult *result)
2051 {
2052     assert(attr != NULL);
2053 
2054     PackageManager *mp;
2055 
2056     // This REALLY needs some commenting
2057     for (mp = INSTALLED_PACKAGE_LISTS; mp != NULL; mp = mp->next)
2058     {
2059         if (strcmp(mp->manager, attr->packages.package_list_command) == 0)
2060         {
2061             break;
2062         }
2063     }
2064 
2065     Log(LOG_LEVEL_VERBOSE, "PatchMatch: looking for %s to [%s] with given (%s,%s,%s) [name,version,arch] in package manager %s",
2066         mode, PackageVersionComparatorToString(attr->packages.package_select), n, v, a, mp->manager);
2067 
2068     for (PackageItem *pi = mp->patch_list; pi != NULL; pi = pi->next)
2069     {
2070         if (FullTextMatch(ctx, n, pi->name)) /* Check regexes */
2071         {
2072             Log(LOG_LEVEL_VERBOSE, "PatchMatch: regular expression match succeeded for %s against %s", n, pi->name);
2073             return VERCMP_MATCH;
2074         }
2075         else
2076         {
2077             VersionCmpResult res = ComparePackages(ctx, n, v, a, pi, attr, pp, mode, result);
2078             if (res != VERCMP_NO_MATCH)
2079             {
2080                 Log(LOG_LEVEL_VERBOSE, "PatchMatch: patch comparison for %s was decisive: %s", pi->name, res == VERCMP_MATCH ? "MATCH" : "ERROR");
2081                 return res;
2082             }
2083         }
2084     }
2085 
2086     Log(LOG_LEVEL_VERBOSE, "PatchMatch did not match the constraints of promise (%s,%s,%s) [name,version,arch]", n, v, a);
2087     return VERCMP_NO_MATCH;
2088 }
2089 
2090 /**
2091    @brief Finds a specific package (n,v,a) [name, version, architecture] as specified by Attributes attr
2092 
2093    Called by CheckPackageState.
2094 
2095    Goes through all the installed packages to find matches for the given attributes.
2096 
2097    The package manager is checked against attr.packages.package_list_command.
2098 
2099    The (n,v,a) search is done with ComparePackages.
2100 
2101    @param ctx [in] The evaluation context
2102    @param n [in] the specific name
2103    @param v [in] the specific version
2104    @param a [in] the specific architecture
2105    @param attr [in] the Attributes specifying how to compare
2106    @param pp [in] the Promise for this operation
2107    @param mode [in] the operating mode, informational for logging
2108    @returns the version comparison result
2109 */
PackageMatch(EvalContext * ctx,const char * n,const char * v,const char * a,const Attributes * attr,const Promise * pp,const char * mode,PromiseResult * result)2110 static VersionCmpResult PackageMatch(EvalContext *ctx,
2111                                      const char *n, const char *v, const char *a,
2112                                      const Attributes *attr,
2113                                      const Promise *pp,
2114                                      const char* mode,
2115                                      PromiseResult *result)
2116 /*
2117  * Returns VERCMP_MATCH if any installed packages match (n,v,a), VERCMP_NO_MATCH otherwise, VERCMP_ERROR on error.
2118  * The mode is informational
2119  */
2120 {
2121     assert(attr != NULL);
2122 
2123     PackageManager *mp = NULL;
2124 
2125     // This REALLY needs some commenting
2126     for (mp = INSTALLED_PACKAGE_LISTS; mp != NULL; mp = mp->next)
2127     {
2128         if (strcmp(mp->manager, attr->packages.package_list_command) == 0)
2129         {
2130             break;
2131         }
2132     }
2133 
2134     Log(LOG_LEVEL_VERBOSE, "PackageMatch: looking for %s (%s,%s,%s) [name,version,arch] in package manager %s", mode, n, v, a, mp->manager);
2135 
2136     for (PackageItem *pi = mp->pack_list; pi != NULL; pi = pi->next)
2137     {
2138         VersionCmpResult res = ComparePackages(ctx, n, v, a, pi, attr, pp, mode, result);
2139 
2140         if (res != VERCMP_NO_MATCH)
2141         {
2142             Log(LOG_LEVEL_VERBOSE, "PackageMatch: package comparison for %s %s was decisive: %s", mode, pi->name, res == VERCMP_MATCH ? "MATCH" : "ERROR");
2143             return res;
2144         }
2145     }
2146 
2147     Log(LOG_LEVEL_VERBOSE, "PackageMatch did not find %s packages to match the constraints of promise (%s,%s,%s) [name,version,arch]", mode, n, v, a);
2148     return VERCMP_NO_MATCH;
2149 }
2150 
2151 /**
2152    @brief Check if the operation should be scheduled based on the package policy, if the package matches, and if it's installed
2153 
2154    Called by CheckPackageState.
2155 
2156    Uses a.packages.package_policy to determine operating mode.
2157 
2158    The use of matches and installed depends on the package_policy:
2159    * PACKAGE_ACTION_DELETE: schedule if (matches AND installed)
2160    * PACKAGE_ACTION_REINSTALL: schedule if (matches AND installed)
2161    * all other policies: schedule if (not matches OR not installed)
2162 
2163    @param ctx [in] The evaluation context
2164    @param a [in] the Attributes specifying the package policy
2165    @param pp [in] the Promise for this operation
2166    @param matches [in] whether the package matches
2167    @param installed [in] whether the package is installed
2168    @returns whether the package operation should be scheduled
2169 */
WillSchedulePackageOperation(EvalContext * ctx,const Attributes * a,const Promise * pp,int matches,int installed)2170 static bool WillSchedulePackageOperation(EvalContext *ctx, const Attributes *a, const Promise *pp, int matches, int installed)
2171 {
2172     assert(a != NULL);
2173     assert(pp != NULL);
2174 
2175     PackageAction policy = a->packages.package_policy;
2176 
2177     Log(LOG_LEVEL_DEBUG, "WillSchedulePackageOperation: on entry, action %s: package %s matches = %s, installed = %s.",
2178         PackageAction2String(policy), pp->promiser, matches ? "yes" : "no", installed ? "yes" : "no");
2179 
2180     switch (policy)
2181     {
2182     case PACKAGE_ACTION_DELETE:
2183         if (matches && installed)
2184         {
2185             Log(LOG_LEVEL_VERBOSE, "WillSchedulePackageOperation: Package %s to be deleted is installed.", pp->promiser);
2186             return true;
2187         }
2188         else
2189         {
2190             Log(LOG_LEVEL_DEBUG, "WillSchedulePackageOperation: Package %s can't be deleted if it's not installed, NOOP.", pp->promiser);
2191             cfPS_HELPER_1ARG(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "Package %s to be deleted does not exist anywhere",
2192                  pp->promiser);
2193         }
2194         break;
2195 
2196     case PACKAGE_ACTION_REINSTALL:
2197         if (matches && installed)
2198         {
2199             Log(LOG_LEVEL_VERBOSE, "WillSchedulePackageOperation: Package %s to be reinstalled is already installed.", pp->promiser);
2200             return true;
2201         }
2202         else
2203         {
2204             Log(LOG_LEVEL_DEBUG, "WillSchedulePackageOperation: Package %s already installed, NOOP.", pp->promiser);
2205             cfPS_HELPER_1ARG(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "Package '%s' already installed and matches criteria",
2206                  pp->promiser);
2207         }
2208         break;
2209 
2210     default:
2211         if (!matches) // why do we schedule a 'not matched' operation?
2212         {
2213             return true;
2214         }
2215         else if (!installed) // matches and not installed
2216         {
2217             return true;
2218         }
2219         else // matches and installed
2220         {
2221             Log(LOG_LEVEL_DEBUG, "WillSchedulePackageOperation: Package %s already installed, NOOP.", pp->promiser);
2222             cfPS_HELPER_1ARG(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "Package '%s' already installed and matches criteria",
2223                  pp->promiser);
2224         }
2225         break;
2226     }
2227 
2228     return false;
2229 }
2230 
2231 /**
2232    @brief Checks the state of a specific package (name,version,arch) as specified by Attributes a
2233 
2234    Called by VerifyPromisedPackage.
2235 
2236    * copies a into a2, overrides a2.packages.package_select to PACKAGE_VERSION_COMPARATOR_EQ
2237    * VersionCmpResult installed = check if (name,*,arch) is installed with PackageMatch (note version override!)
2238    * if PackageMatch returned an error, fail the promise
2239    * VersionCmpResult matches = check if (name,version,arch) is installed with PackageMatch
2240    * if PackageMatch returned an error, fail the promise
2241    * if WillSchedulePackageOperation with "matches" and "installed" passes, call SchedulePackageOp on the package
2242 
2243    @param ctx [in] The evaluation context
2244    @param a [in] the Attributes specifying how to compare
2245    @param pp [in] the Promise for this operation
2246    @param name [in] the specific name
2247    @param version [in] the specific version
2248    @param arch [in] the specific architecture
2249    @param no_version [in] ignore the version, be cool
2250    @returns the promise result
2251 */
CheckPackageState(EvalContext * ctx,const Attributes * a,const Promise * pp,const char * name,const char * version,const char * arch,bool no_version)2252 static PromiseResult CheckPackageState(EvalContext *ctx, const Attributes *a, const Promise *pp, const char *name, const char *version,
2253                                        const char *arch, bool no_version)
2254 {
2255     assert(a != NULL);
2256     assert(pp != NULL); // Dereferenced in cfPS macros
2257 
2258     PromiseResult result = PROMISE_RESULT_NOOP;
2259 
2260     /* Horrible */
2261     Attributes a2 = *a;
2262     a2.packages.package_select = PACKAGE_VERSION_COMPARATOR_EQ;
2263 
2264     VersionCmpResult installed = PackageMatch(ctx, name, "*", arch, &a2, pp, "[installed]", &result);
2265     Log(LOG_LEVEL_VERBOSE, "CheckPackageState: Installed package match for (%s,%s,%s) [name,version,arch] was decisive: %s",
2266         name, "*", arch, installed == VERCMP_MATCH ? "MATCH" : "ERROR-OR-NOMATCH");
2267 
2268     if (installed == VERCMP_ERROR)
2269     {
2270         cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, &a2, "Failure trying to compare installed package versions");
2271         result = PromiseResultUpdate_HELPER(pp, result, PROMISE_RESULT_FAIL);
2272         return result;
2273     }
2274 
2275     VersionCmpResult matches = PackageMatch(ctx, name, version, arch, &a2, pp, "[available]", &result);
2276     Log(LOG_LEVEL_VERBOSE, "CheckPackageState: Available package match for (%s,%s,%s) [name,version,arch] was decisive: %s",
2277         name, version, arch, matches == VERCMP_MATCH ? "MATCH" : "ERROR-OR-NOMATCH");
2278 
2279     if (matches == VERCMP_ERROR)
2280     {
2281         cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, &a2, "Failure trying to compare available package versions");
2282         result = PromiseResultUpdate_HELPER(pp, result, PROMISE_RESULT_FAIL);
2283         return result;
2284     }
2285 
2286     if (WillSchedulePackageOperation(ctx, &a2, pp, matches, installed))
2287     {
2288         Log(LOG_LEVEL_VERBOSE, "CheckPackageState: matched package (%s,%s,%s) [name,version,arch]; scheduling operation", name, version, arch);
2289         return SchedulePackageOp(ctx, name, version, arch, installed, matches, no_version, a, pp);
2290     }
2291 
2292     return result;
2293 }
2294 
2295 /**
2296    @brief Verifies a promised patch operation as defined by a and pp
2297 
2298    Called by VerifyPackagesPromise for the patch operation.
2299 
2300    * package name is pp->promiser
2301    * installed and matches counts = 0
2302    * copies a into a2 and overrides a2.packages.package_select to PACKAGE_VERSION_COMPARATOR_EQ
2303    * promise result starts as NOOP
2304    * if package version is given
2305    * * for arch = each architecture requested in a2, or (if none given) any architecture "*"
2306    * * * installed1 = PatchMatch(a2, name, any version "*", any architecture "*")
2307    * * * matches1 = PatchMatch(a2, name, requested version, arch)
2308    * * * if either installed1 or matches1 failed, return promise error
2309    * * * else, installed += installed1; matches += matches1
2310    * else if package_version_regex is given
2311    * * assume that package_name_regex and package_arch_regex are also given and use the 3 regexes to extract name, version, arch
2312    * * * installed = PatchMatch(a2, matched name, any version "*", any architecture "*")
2313    * * * matches = PatchMatch(a2, matched name, matched version, matched architecture)
2314    * * * if either installed or matches failed, return promise error
2315    * else (no explicit version is given) (SAME LOOP AS EXPLICIT VERSION LOOP ABOVE)
2316    * * no_version = true
2317    * * for arch = each architecture requested in a2, or (if none given) any architecture "*"
2318    * * * requested version = any version '*'
2319    * * * installed1 = PatchMatch(a2, name, any version "*", any architecture "*")
2320    * * * matches1 = PatchMatch(a2, name, requested version '*', arch)
2321    * * * if either installed1 or matches1 failed, return promise error
2322    * * * else, installed += installed1; matches += matches1
2323    * finally, call SchedulePackageOp with the found name, version, arch, installed, matches, no_version
2324 
2325    @param ctx [in] The evaluation context
2326    @param a [in] the Attributes specifying how to compare
2327    @param pp [in] the Promise for this operation
2328    @returns the promise result (failure or NOOP)
2329 */
VerifyPromisedPatch(EvalContext * ctx,const Attributes * a,const Promise * pp)2330 static PromiseResult VerifyPromisedPatch(EvalContext *ctx, const Attributes *a, const Promise *pp)
2331 {
2332     assert(a != NULL);
2333     assert(pp != NULL);
2334 
2335     char version[CF_MAXVARSIZE];
2336     char name[CF_MAXVARSIZE];
2337     char arch[CF_MAXVARSIZE];
2338     char *package = pp->promiser;
2339     int matches = 0, installed = 0, no_version = false;
2340     Rlist *rp;
2341 
2342     /* Horrible */
2343     Attributes a2 = *a;
2344     a2.packages.package_select = PACKAGE_VERSION_COMPARATOR_EQ;
2345 
2346     PromiseResult result = PROMISE_RESULT_NOOP;
2347     if (a2.packages.package_version) /* The version is specified explicitly */
2348     {
2349         // Note this loop will run if rp is NULL
2350         for (rp = a2.packages.package_architectures; ; rp = rp->next)
2351         {
2352             strlcpy(name, pp->promiser, CF_MAXVARSIZE);
2353             strlcpy(version, a2.packages.package_version, CF_MAXVARSIZE);
2354             strlcpy(arch,
2355                     (rp == NULL) ? "*" : RlistScalarValue(rp),
2356                     CF_MAXVARSIZE);
2357             VersionCmpResult installed1 = PatchMatch(ctx, name, "*", "*", &a2, pp, "[installed1]", &result);
2358             VersionCmpResult matches1 = PatchMatch(ctx, name, version, arch, &a2, pp, "[available1]", &result);
2359 
2360             if ((installed1 == VERCMP_ERROR) || (matches1 == VERCMP_ERROR))
2361             {
2362                 cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, &a2, "Failure trying to compare package versions");
2363                 result = PromiseResultUpdate_HELPER(pp, result, PROMISE_RESULT_FAIL);
2364                 return result;
2365             }
2366 
2367             installed += installed1;
2368             matches += matches1;
2369 
2370             if (rp == NULL) break; // Note we exit the loop explicitly here
2371         }
2372     }
2373     else if (a2.packages.package_version_regex) // version is not given, but a version regex is
2374     {
2375         /* The name, version and arch are to be extracted from the promiser */
2376         strlcpy(version, ExtractFirstReference(a2.packages.package_version_regex, package), CF_MAXVARSIZE);
2377         strlcpy(name, ExtractFirstReference(a2.packages.package_name_regex, package), CF_MAXVARSIZE);
2378         strlcpy(arch, ExtractFirstReference(a2.packages.package_arch_regex, package), CF_MAXVARSIZE);
2379         installed = PatchMatch(ctx, name, "*", "*", &a2, pp, "[installed]", &result);
2380         matches = PatchMatch(ctx, name, version, arch, &a2, pp, "[available]", &result);
2381 
2382         if ((installed == VERCMP_ERROR) || (matches == VERCMP_ERROR))
2383         {
2384             cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, &a2, "Failure trying to compare package versions");
2385             result = PromiseResultUpdate_HELPER(pp, result, PROMISE_RESULT_FAIL);
2386             return result;
2387         }
2388     }
2389     else // the desired package version was not specified
2390     {
2391         no_version = true;
2392 
2393         // Note this loop will run if rp is NULL
2394         for (rp = a2.packages.package_architectures; ; rp = rp->next)
2395         {
2396             strlcpy(name, pp->promiser, CF_MAXVARSIZE);
2397             strlcpy(version, "*", CF_MAXVARSIZE);
2398             strlcpy(arch,
2399                     (rp == NULL) ? "*" : RlistScalarValue(rp),
2400                     CF_MAXVARSIZE);
2401             VersionCmpResult installed1 = PatchMatch(ctx, name, "*", "*", &a2, pp, "[installed1]", &result);
2402             VersionCmpResult matches1 = PatchMatch(ctx, name, version, arch, &a2, pp, "[available1]", &result);
2403 
2404             if ((installed1 == VERCMP_ERROR) || (matches1 == VERCMP_ERROR))
2405             {
2406                 cfPS_HELPER_0ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, &a2, "Failure trying to compare package versions");
2407                 result = PromiseResultUpdate_HELPER(pp, result, PROMISE_RESULT_FAIL);
2408                 return result;
2409             }
2410 
2411             installed += installed1;
2412             matches += matches1;
2413 
2414             if (rp == NULL) break; // Note we exit the loop explicitly here
2415         }
2416     }
2417 
2418     Log(LOG_LEVEL_VERBOSE, "%d patch(es) matching the name '%s' already installed", installed, name);
2419     Log(LOG_LEVEL_VERBOSE, "%d patch(es) match the promise body's criteria fully", matches);
2420 
2421     SchedulePackageOp(ctx, name, version, arch, installed, matches, no_version, a, pp);
2422 
2423     return PROMISE_RESULT_NOOP;
2424 }
2425 
2426 /**
2427    @brief Verifies a promised package operation as defined by a and pp
2428 
2429    Called by VerifyPackagesPromise for any non-patch operation.
2430 
2431    * package name is pp->promiser
2432    * promise result starts as NOOP
2433    * if package version is given
2434    * * if no architecture given, the promise result comes from CheckPackageState with name, version, any architecture '*', no_version=false
2435    * * else if architectures were given, the promise result comes from CheckPackageState with name, version, arch, no_version=false FOR EACH ARCHITECTURE
2436    * else if package_version_regex is given
2437    * * assume that package_name_regex and package_arch_regex are also given and use the 3 regexes to extract name, version, arch
2438    * * if the arch extraction failed, use any architecture '*'
2439    * * the promise result comes from CheckPackageState with name, version, arch, no_version=false)
2440    * else (no explicit version is given) (SAME LOOP AS EXPLICIT VERSION LOOP ABOVE)
2441    * * if no architecture given, the promise result comes from CheckPackageState with name, any version "*", any architecture '*', no_version=true
2442    * * else if architectures were given, the promise result comes from CheckPackageState with name, any version "*", arch, no_version=true FOR EACH ARCHITECTURE
2443 
2444    @param ctx [in] The evaluation context
2445    @param a [in] the Attributes specifying how to compare
2446    @param pp [in] the Promise for this operation
2447    @returns the promise result as set by CheckPackageState
2448 */
VerifyPromisedPackage(EvalContext * ctx,const Attributes * a,const Promise * pp)2449 static PromiseResult VerifyPromisedPackage(EvalContext *ctx, const Attributes *a, const Promise *pp)
2450 {
2451     assert(a != NULL);
2452     assert(pp != NULL);
2453 
2454     const char *package = pp->promiser;
2455     const Packages *const pkgs = &(a->packages);
2456 
2457     PromiseResult result = PROMISE_RESULT_NOOP;
2458     if (pkgs->package_version)
2459     {
2460         /* The version is specified separately */
2461         Log(LOG_LEVEL_VERBOSE, "Package version %s specified explicitly in promise body", pkgs->package_version);
2462 
2463         if (pkgs->package_architectures == NULL)
2464         {
2465             Log(LOG_LEVEL_VERBOSE, " ... trying any arch '*'");
2466             result = PromiseResultUpdate_HELPER(pp, result, CheckPackageState(ctx, a, pp, package, pkgs->package_version, "*", false));
2467         }
2468         else
2469         {
2470             for (Rlist *rp = pkgs->package_architectures; rp != NULL; rp = rp->next)
2471             {
2472                 Log(LOG_LEVEL_VERBOSE, " ... trying listed arch '%s'", RlistScalarValue(rp));
2473                 result = PromiseResultUpdate_HELPER(pp, result,
2474                                              CheckPackageState(ctx, a, pp, package, pkgs->package_version,
2475                                                                RlistScalarValue(rp), false));
2476             }
2477         }
2478     }
2479     else if (pkgs->package_version_regex)
2480     {
2481         /* The name, version and arch are to be extracted from the promiser */
2482         Log(LOG_LEVEL_VERBOSE, "Package version %s specified implicitly in promiser's name", pkgs->package_version_regex);
2483 
2484         char version[CF_MAXVARSIZE];
2485         char name[CF_MAXVARSIZE];
2486         char arch[CF_MAXVARSIZE];
2487         strlcpy(version, ExtractFirstReference(pkgs->package_version_regex, package), CF_MAXVARSIZE);
2488         strlcpy(name, ExtractFirstReference(pkgs->package_name_regex, package), CF_MAXVARSIZE);
2489         strlcpy(arch, ExtractFirstReference(pkgs->package_arch_regex, package), CF_MAXVARSIZE);
2490 
2491         if (!arch[0])
2492         {
2493             strlcpy(arch, "*", CF_MAXVARSIZE);
2494         }
2495 
2496         if (strcmp(arch, "CF_NOMATCH") == 0)    // no match on arch regex, use any arch
2497         {
2498             strlcpy(arch, "*", CF_MAXVARSIZE);
2499         }
2500 
2501         Log(LOG_LEVEL_VERBOSE, " ... trying arch '%s' and version '%s'", arch, version);
2502         result = PromiseResultUpdate_HELPER(pp, result, CheckPackageState(ctx, a, pp, name, version, arch, false));
2503     }
2504     else
2505     {
2506         Log(LOG_LEVEL_VERBOSE, "Package version was not specified");
2507 
2508         if (pkgs->package_architectures == NULL)
2509         {
2510             Log(LOG_LEVEL_VERBOSE, " ... trying any arch '*' and any version '*'");
2511             result = PromiseResultUpdate_HELPER(pp, result, CheckPackageState(ctx, a, pp, package, "*", "*", true));
2512         }
2513         else
2514         {
2515             for (Rlist *rp = pkgs->package_architectures; rp != NULL; rp = rp->next)
2516             {
2517                 Log(LOG_LEVEL_VERBOSE, " ... trying listed arch '%s' and any version '*'", RlistScalarValue(rp));
2518                 result = PromiseResultUpdate_HELPER(pp, result, CheckPackageState(ctx, a, pp, package, "*", RlistScalarValue(rp), true));
2519             }
2520         }
2521     }
2522 
2523     return result;
2524 }
2525 
2526 /** Execute scheduled operations **/
2527 
2528 /**
2529    @brief Central dispatcher for scheduled operations
2530 
2531    Called by ExecutePackageSchedule.
2532 
2533    Almost identical to ExecutePatch.
2534 
2535    * verify = false
2536    * for each PackageManager pm in the schedule
2537    * * if pm->pack_list is empty or the scheduled pm->action doesn't match the given action, skip this pm
2538    * * estimate the size of the command string from pm->pack_list and pm->policy (SHOULD USE Buffer)
2539    * * from the first PackageItem in pm->pack_list, get the Promise pp and its Attributes a
2540    * * switch(action)
2541    * * * case ADD:
2542    * * * * command_string = a.packages.package_add_command + estimated_size room for package names
2543    * * * case DELETE:
2544    * * * * command_string = a.packages.package_delete_command + estimated_size room for package names
2545    * * * case UPDATE:
2546    * * * * command_string = a.packages.package_update_command + estimated_size room for package names
2547    * * * case VERIFY:
2548    * * * * command_string = a.packages.package_verify_command + estimated_size room for package names
2549    * * * * verify = true
2550 
2551    * * if the command string ends with $, run it with ExecPackageCommand(command, verify) and magic the promise evaluation
2552    * * else, switch(pm->policy)
2553    * * * case INDIVIDUAL:
2554    * * * * for each PackageItem in the pack_list, build the command and run it with ExecPackageCommand(command, verify) and magic the promise evaluation
2555    * * * * NOTE with file repositories and ADD/UPDATE operations, the package name gets the repo path too
2556    * * * * NOTE special treatment of PACKAGE_IGNORED_CFE_INTERNAL
2557    * * * case BULK:
2558    * * * * for all PackageItems in the pack_list, build the command and run it with ExecPackageCommand(command, verify) and magic the promise evaluation
2559    * * * * NOTE with file repositories and ADD/UPDATE operations, the package name gets the repo path too
2560    * * * * NOTE special treatment of PACKAGE_IGNORED_CFE_INTERNAL
2561    * * clean up command_string
2562    * if the operation was not a verification, InvalidateSoftwareCache
2563 
2564    @param ctx [in] The evaluation context
2565    @param schedule [in] the PackageManager list with the operations schedule
2566    @param action [in] the PackageAction desired
2567    @returns boolean success/fail (fail only on ProgrammingError, should never happen)
2568 */
ExecuteSchedule(EvalContext * ctx,const PackageManager * schedule,PackageAction action)2569 static bool ExecuteSchedule(EvalContext *ctx, const PackageManager *schedule, PackageAction action)
2570 {
2571     bool verify = false;
2572 
2573     for (const PackageManager *pm = schedule; pm != NULL; pm = pm->next)
2574     {
2575         if (pm->action != action)
2576         {
2577             continue;
2578         }
2579 
2580         if (pm->pack_list == NULL)
2581         {
2582             continue;
2583         }
2584 
2585         size_t estimated_size = 0;
2586 
2587         for (const PackageItem *pi = pm->pack_list; pi != NULL; pi = pi->next)
2588         {
2589             size_t size = strlen(pi->name) + strlen("  ");
2590 
2591             switch (pm->policy)
2592             {
2593             case PACKAGE_ACTION_POLICY_INDIVIDUAL:
2594 
2595                 if (size > estimated_size)
2596                 {
2597                     estimated_size = size + CF_MAXVARSIZE;
2598                 }
2599                 break;
2600 
2601             case PACKAGE_ACTION_POLICY_BULK:
2602 
2603                 estimated_size += size + CF_MAXVARSIZE;
2604                 break;
2605 
2606             default:
2607                 break;
2608             }
2609         }
2610 
2611         const Promise *const pp = pm->pack_list->pp;
2612         Attributes a = GetPackageAttributes(ctx, pp);
2613         char *command_string = NULL;
2614 
2615         switch (action)
2616         {
2617         case PACKAGE_ACTION_ADD:
2618 
2619             Log(LOG_LEVEL_VERBOSE, "Execute scheduled package addition");
2620 
2621             if (a.packages.package_add_command == NULL)
2622             {
2623                 ProgrammingError("Package add command undefined");
2624                 return false;
2625             }
2626 
2627             Log(LOG_LEVEL_INFO, "Installing %-.39s...", pp->promiser);
2628 
2629             estimated_size += strlen(a.packages.package_add_command) + 2;
2630             command_string = xmalloc(estimated_size);
2631             strcpy(command_string, a.packages.package_add_command);
2632             break;
2633 
2634         case PACKAGE_ACTION_DELETE:
2635 
2636             Log(LOG_LEVEL_VERBOSE, "Execute scheduled package deletion");
2637 
2638             if (a.packages.package_delete_command == NULL)
2639             {
2640                 ProgrammingError("Package delete command undefined");
2641                 return false;
2642             }
2643 
2644             Log(LOG_LEVEL_INFO, "Deleting %-.39s...", pp->promiser);
2645 
2646             estimated_size += strlen(a.packages.package_delete_command) + 2;
2647             command_string = xmalloc(estimated_size);
2648             strcpy(command_string, a.packages.package_delete_command);
2649             break;
2650 
2651         case PACKAGE_ACTION_UPDATE:
2652 
2653             Log(LOG_LEVEL_VERBOSE, "Execute scheduled package update");
2654 
2655             if (a.packages.package_update_command == NULL)
2656             {
2657                 ProgrammingError("Package update command undefined");
2658                 return false;
2659             }
2660 
2661             Log(LOG_LEVEL_INFO, "Updating %-.39s...", pp->promiser);
2662 
2663             estimated_size += strlen(a.packages.package_update_command) + 2;
2664             command_string = xcalloc(1, estimated_size);
2665             strcpy(command_string, a.packages.package_update_command);
2666             break;
2667 
2668         case PACKAGE_ACTION_VERIFY:
2669 
2670             Log(LOG_LEVEL_VERBOSE, "Execute scheduled package verification");
2671 
2672             if (a.packages.package_verify_command == NULL)
2673             {
2674                 ProgrammingError("Package verify command undefined");
2675                 return false;
2676             }
2677 
2678             estimated_size += strlen(a.packages.package_verify_command) + 2;
2679             command_string = xmalloc(estimated_size);
2680             strcpy(command_string, a.packages.package_verify_command);
2681 
2682             verify = true;
2683             break;
2684 
2685         default:
2686             ProgrammingError("Unknown action attempted");
2687             return false;
2688         }
2689 
2690         /* if the command ends with $ then we assume the package manager does not accept package names */
2691 
2692         if (*(command_string + strlen(command_string) - 1) == '$')
2693         {
2694             *(command_string + strlen(command_string) - 1) = '\0';
2695             Log(LOG_LEVEL_VERBOSE, "Command does not allow arguments");
2696             PromiseResult result = PROMISE_RESULT_NOOP;
2697 
2698             EvalContextStackPushPromiseFrame(ctx, pp);
2699             if (EvalContextStackPushPromiseIterationFrame(ctx, NULL))
2700             {
2701                 if (ExecPackageCommand(ctx, command_string, verify, true, &a, pp, &result))
2702                 {
2703                     Log(LOG_LEVEL_VERBOSE, "Package schedule execution ok (outcome cannot be promised by cf-agent)");
2704                 }
2705                 else
2706                 {
2707                     Log(LOG_LEVEL_ERR, "Package schedule execution failed");
2708                 }
2709 
2710                 EvalContextStackPopFrame(ctx);
2711             }
2712             EvalContextStackPopFrame(ctx);
2713 
2714             EvalContextLogPromiseIterationOutcome(ctx, pp, result);
2715         }
2716         else
2717         {
2718             strcat(command_string, " ");
2719 
2720             Log(LOG_LEVEL_VERBOSE, "Command prefix '%s'", command_string);
2721 
2722             if (pm->policy == PACKAGE_ACTION_POLICY_INDIVIDUAL)
2723             {
2724 
2725                 for (const PackageItem *pi = pm->pack_list; pi != NULL; pi = pi->next)
2726                 {
2727                     const Promise *const ppi = pi->pp;
2728                     Attributes a = GetPackageAttributes(ctx, ppi);
2729 
2730                     const size_t command_len = strlen(command_string);
2731                     char *offset = command_string + command_len;
2732 
2733                     if ((a.packages.package_file_repositories) && ((action == PACKAGE_ACTION_ADD) || (action == PACKAGE_ACTION_UPDATE)))
2734                     {
2735                         const char *sp = PrefixLocalRepository(a.packages.package_file_repositories, pi->name);
2736                         if (sp != NULL)
2737                         {
2738                             strlcat(offset, sp, estimated_size - command_len);
2739                         }
2740                         else
2741                         {
2742                             continue;
2743                         }
2744                     }
2745                     else
2746                     {
2747                         strcat(offset, pi->name);
2748                     }
2749 
2750                     PromiseResult result = PROMISE_RESULT_NOOP;
2751                     EvalContextStackPushPromiseFrame(ctx, ppi);
2752                     if (EvalContextStackPushPromiseIterationFrame(ctx, NULL))
2753                     {
2754                         bool ok = ExecPackageCommand(ctx, command_string, verify, true, &a, ppi, &result);
2755 
2756                         if (StringEqual(pi->name, PACKAGE_IGNORED_CFE_INTERNAL))
2757                         {
2758                             Log(LOG_LEVEL_DEBUG, "ExecuteSchedule: Ignoring outcome for special package '%s'", pi->name);
2759                         }
2760                         else if (ok)
2761                         {
2762                             Log(LOG_LEVEL_VERBOSE,
2763                                 "Package schedule execution ok for '%s' (outcome cannot be promised by cf-agent)",
2764                                   pi->name);
2765                         }
2766                         else
2767                         {
2768                             Log(LOG_LEVEL_ERR, "Package schedule execution failed for '%s'", pi->name);
2769                         }
2770 
2771                         EvalContextStackPopFrame(ctx);
2772                     }
2773                     EvalContextStackPopFrame(ctx);
2774 
2775                     EvalContextLogPromiseIterationOutcome(ctx, ppi, result);
2776 
2777                     *offset = '\0';
2778                 }
2779             }
2780             else if (pm->policy == PACKAGE_ACTION_POLICY_BULK)
2781             {
2782                 for (const PackageItem *pi = pm->pack_list; pi != NULL; pi = pi->next)
2783                 {
2784                     if (pi->name)
2785                     {
2786                         const size_t command_len = strlen(command_string);
2787                         char *offset = command_string + command_len;
2788 
2789                         if (a.packages.package_file_repositories &&
2790                             (action == PACKAGE_ACTION_ADD ||
2791                              action == PACKAGE_ACTION_UPDATE))
2792                         {
2793                             const char *sp = PrefixLocalRepository(a.packages.package_file_repositories, pi->name);
2794                             if (sp != NULL)
2795                             {
2796                                 strlcpy(offset, sp, estimated_size - command_len);
2797                             }
2798                             else
2799                             {
2800                                 break;
2801                             }
2802                         }
2803                         else
2804                         {
2805                             strcpy(offset, pi->name);
2806                         }
2807 
2808                         strcat(command_string, " ");
2809                     }
2810                 }
2811 
2812                 PromiseResult result = PROMISE_RESULT_NOOP;
2813                 EvalContextStackPushPromiseFrame(ctx, pp);
2814                 if (EvalContextStackPushPromiseIterationFrame(ctx, NULL))
2815                 {
2816                     bool ok = ExecPackageCommand(ctx, command_string, verify, true, &a, pp, &result);
2817 
2818                     for (const PackageItem *pi = pm->pack_list; pi != NULL; pi = pi->next)
2819                     {
2820                         if (StringEqual(pi->name, PACKAGE_IGNORED_CFE_INTERNAL))
2821                         {
2822                             Log(LOG_LEVEL_DEBUG, "ExecuteSchedule: Ignoring outcome for special package '%s'", pi->name);
2823                         }
2824                         else if (ok)
2825                         {
2826                             Log(LOG_LEVEL_VERBOSE,
2827                                 "Bulk package schedule execution ok for '%s' (outcome cannot be promised by cf-agent)",
2828                                   pi->name);
2829                         }
2830                         else
2831                         {
2832                             Log(LOG_LEVEL_ERR, "Bulk package schedule execution failed somewhere - unknown outcome for '%s'",
2833                                   pi->name);
2834                         }
2835                     }
2836 
2837                     EvalContextStackPopFrame(ctx);
2838                 }
2839                 EvalContextStackPopFrame(ctx);
2840                 EvalContextLogPromiseIterationOutcome(ctx, pp, result);
2841             }
2842         }
2843 
2844         if (command_string)
2845         {
2846             free(command_string);
2847         }
2848     }
2849 
2850 /* We have performed some modification operation on packages, our cache is invalid */
2851     if (!verify)
2852     {
2853         InvalidateSoftwareCache();
2854     }
2855 
2856     return true;
2857 }
2858 
2859 /**
2860    @brief Central dispatcher for scheduled patch operations
2861 
2862    Called by ExecutePackageSchedule.
2863 
2864    Almost identical to ExecuteSchedule except it only accepts the
2865    PATCH PackageAction and operates on the PackageManagers' patch_list.
2866 
2867    * for each PackageManager pm in the schedule
2868    * * if pm->patch_list is empty or the scheduled pm->action doesn't match the given action, skip this pm
2869    * * estimate the size of the command string from pm->patch_list and pm->policy (SHOULD USE Buffer)
2870    * * from the first PackageItem in pm->patch_list, get the Promise pp and its Attributes a
2871    * * switch(action)
2872    * * * case PATCH:
2873    * * * * command_string = a.packages.package_patch_command + estimated_size room for package names
2874 
2875    * * if the command string ends with $, run it with ExecPackageCommand(command, verify) and magic the promise evaluation
2876    * * else, switch(pm->policy)
2877    * * * case INDIVIDUAL:
2878    * * * * for each PackageItem in the patch_list, build the command and run it with ExecPackageCommand(command, verify) and magic the promise evaluation
2879    * * * * NOTE with file repositories and ADD/UPDATE operations, the package name gets the repo path too
2880    * * * * NOTE special treatment of PACKAGE_IGNORED_CFE_INTERNAL
2881    * * * case BULK:
2882    * * * * for all PackageItems in the patch_list, build the command and run it with ExecPackageCommand(command, verify) and magic the promise evaluation
2883    * * * * NOTE with file repositories and ADD/UPDATE operations, the package name gets the repo path too
2884    * * * * NOTE special treatment of PACKAGE_IGNORED_CFE_INTERNAL
2885    * * clean up command_string
2886    * InvalidateSoftwareCache
2887 
2888    @param ctx [in] The evaluation context
2889    @param schedule [in] the PackageManager list with the operations schedule
2890    @param action [in] the PackageAction desired
2891    @returns boolean success/fail (fail only on ProgrammingError, should never happen)
2892 */
ExecutePatch(EvalContext * ctx,const PackageManager * schedule,PackageAction action)2893 static bool ExecutePatch(EvalContext *ctx, const PackageManager *schedule, PackageAction action)
2894 {
2895     for (const PackageManager *pm = schedule; pm != NULL; pm = pm->next)
2896     {
2897         if (pm->action != action)
2898         {
2899             continue;
2900         }
2901 
2902         if (pm->patch_list == NULL)
2903         {
2904             continue;
2905         }
2906 
2907         size_t estimated_size = 0;
2908 
2909         for (const PackageItem *pi = pm->patch_list; pi != NULL; pi = pi->next)
2910         {
2911             size_t size = strlen(pi->name) + strlen("  ");
2912 
2913             switch (pm->policy)
2914             {
2915             case PACKAGE_ACTION_POLICY_INDIVIDUAL:
2916                 if (size > estimated_size)
2917                 {
2918                     estimated_size = size;
2919                 }
2920                 break;
2921 
2922             case PACKAGE_ACTION_POLICY_BULK:
2923                 estimated_size += size;
2924                 break;
2925 
2926             default:
2927                 break;
2928             }
2929         }
2930 
2931         char *command_string = NULL;
2932         const Promise *const pp = pm->patch_list->pp;
2933         Attributes a = GetPackageAttributes(ctx, pp);
2934 
2935         if (action == PACKAGE_ACTION_PATCH)
2936         {
2937             Log(LOG_LEVEL_VERBOSE, "Execute scheduled package patch");
2938 
2939             if (a.packages.package_patch_command == NULL)
2940             {
2941                 ProgrammingError("Package patch command undefined");
2942                 return false;
2943             }
2944 
2945             command_string = xmalloc(estimated_size + strlen(a.packages.package_patch_command) + 2);
2946             strcpy(command_string, a.packages.package_patch_command);
2947         }
2948         else
2949         {
2950             ProgrammingError("Unknown action attempted");
2951             return false;
2952         }
2953 
2954         /* if the command ends with $ then we assume the package manager does not accept package names */
2955 
2956         if (command_string[strlen(command_string) - 1] == '$')
2957         {
2958             command_string[strlen(command_string) - 1] = '\0';
2959             Log(LOG_LEVEL_VERBOSE, "Command does not allow arguments");
2960 
2961             PromiseResult result = PROMISE_RESULT_NOOP;
2962             EvalContextStackPushPromiseFrame(ctx, pp);
2963             if (EvalContextStackPushPromiseIterationFrame(ctx, NULL))
2964             {
2965                 if (ExecPackageCommand(ctx, command_string, false, true, &a, pp, &result))
2966                 {
2967                     Log(LOG_LEVEL_VERBOSE, "Package patching seemed to succeed (outcome cannot be promised by cf-agent)");
2968                 }
2969                 else
2970                 {
2971                     Log(LOG_LEVEL_ERR, "Package patching failed");
2972                 }
2973 
2974                 EvalContextStackPopFrame(ctx);
2975             }
2976             EvalContextStackPopFrame(ctx);
2977             EvalContextLogPromiseIterationOutcome(ctx, pp, result);
2978         }
2979         else
2980         {
2981             strcat(command_string, " ");
2982 
2983             Log(LOG_LEVEL_VERBOSE, "Command prefix '%s'", command_string);
2984 
2985             switch (pm->policy)
2986             {
2987             case PACKAGE_ACTION_POLICY_INDIVIDUAL:
2988                 for (const PackageItem *pi = pm->patch_list; pi != NULL; pi = pi->next)
2989                 {
2990                     char *offset = command_string + strlen(command_string);
2991 
2992                     strcat(offset, pi->name);
2993 
2994                     PromiseResult result = PROMISE_RESULT_NOOP;
2995                     EvalContextStackPushPromiseFrame(ctx, pp);
2996                     if (EvalContextStackPushPromiseIterationFrame(ctx, NULL))
2997                     {
2998                         bool ok = ExecPackageCommand(ctx, command_string, false, true, &a, pp, &result);
2999 
3000                         if (StringEqual(pi->name, PACKAGE_IGNORED_CFE_INTERNAL))
3001                         {
3002                             Log(LOG_LEVEL_DEBUG, "ExecutePatch: Ignoring outcome for special package '%s'", pi->name);
3003                         }
3004                         else if (ok)
3005                         {
3006                             Log(LOG_LEVEL_VERBOSE,
3007                                 "Package schedule execution ok for '%s' (outcome cannot be promised by cf-agent)",
3008                                   pi->name);
3009                         }
3010                         else
3011                         {
3012                             Log(LOG_LEVEL_ERR, "Package schedule execution failed for '%s'", pi->name);
3013                         }
3014 
3015                         EvalContextStackPopFrame(ctx);
3016                     }
3017                     EvalContextStackPopFrame(ctx);
3018                     EvalContextLogPromiseIterationOutcome(ctx, pp, result);
3019 
3020                     *offset = '\0';
3021                 }
3022 
3023                 break;
3024 
3025             case PACKAGE_ACTION_POLICY_BULK:
3026                 for (const PackageItem *pi = pm->patch_list; pi != NULL; pi = pi->next)
3027                 {
3028                     if (pi->name)
3029                     {
3030                         strcat(command_string, pi->name);
3031                         strcat(command_string, " ");
3032                     }
3033                 }
3034 
3035                 PromiseResult result = PROMISE_RESULT_NOOP;
3036                 EvalContextStackPushPromiseFrame(ctx, pp);
3037                 if (EvalContextStackPushPromiseIterationFrame(ctx, NULL))
3038                 {
3039                     bool ok = ExecPackageCommand(ctx, command_string, false, true, &a, pp, &result);
3040 
3041                     for (const PackageItem *pi = pm->patch_list; pi != NULL; pi = pi->next)
3042                     {
3043                         if (StringEqual(pi->name, PACKAGE_IGNORED_CFE_INTERNAL))
3044                         {
3045                             Log(LOG_LEVEL_DEBUG, "ExecutePatch: Ignoring outcome for special package '%s'", pi->name);
3046                         }
3047                         else if (ok)
3048                         {
3049                             Log(LOG_LEVEL_VERBOSE,
3050                                 "Bulk package schedule execution ok for '%s' (outcome cannot be promised by cf-agent)",
3051                                   pi->name);
3052                         }
3053                         else
3054                         {
3055                             Log(LOG_LEVEL_ERR, "Bulk package schedule execution failed somewhere - unknown outcome for '%s'",
3056                                   pi->name);
3057                         }
3058                     }
3059 
3060                     EvalContextStackPopFrame(ctx);
3061                 }
3062                 EvalContextStackPopFrame(ctx);
3063                 EvalContextLogPromiseIterationOutcome(ctx, pp, result);
3064                 break;
3065 
3066             default:
3067                 break;
3068             }
3069 
3070         }
3071 
3072         if (command_string)
3073         {
3074             free(command_string);
3075         }
3076     }
3077 
3078 /* We have performed some operation on packages, our cache is invalid */
3079     InvalidateSoftwareCache();
3080 
3081     return true;
3082 }
3083 
3084 /**
3085    @brief Ordering manager for scheduled package operations
3086 
3087    Called by ExecuteScheduledPackages.
3088 
3089    * ExecuteSchedule(schedule, DELETE)
3090    * ExecuteSchedule(schedule, ADD)
3091    * ExecuteSchedule(schedule, UPDATE)
3092    * ExecutePatch(schedule, PATCH)
3093    * ExecuteSchedule(schedule, VERIFY)
3094 
3095    @param ctx [in] The evaluation context
3096    @param schedule [in] the PackageManager list with the operations schedule
3097 */
ExecutePackageSchedule(EvalContext * ctx,PackageManager * schedule)3098 static void ExecutePackageSchedule(EvalContext *ctx, PackageManager *schedule)
3099 {
3100         Log(LOG_LEVEL_VERBOSE, "Offering the following package-promise suggestions to the managers");
3101 
3102     /* Normal ordering */
3103 
3104     Log(LOG_LEVEL_VERBOSE, "Deletion schedule...");
3105     if (!ExecuteSchedule(ctx, schedule, PACKAGE_ACTION_DELETE))
3106     {
3107         Log(LOG_LEVEL_ERR, "Aborting package schedule");
3108         return;
3109     }
3110 
3111     Log(LOG_LEVEL_VERBOSE, "Addition schedule...");
3112     if (!ExecuteSchedule(ctx, schedule, PACKAGE_ACTION_ADD))
3113     {
3114         return;
3115     }
3116 
3117     Log(LOG_LEVEL_VERBOSE, "Update schedule...");
3118     if (!ExecuteSchedule(ctx, schedule, PACKAGE_ACTION_UPDATE))
3119     {
3120         return;
3121     }
3122 
3123     Log(LOG_LEVEL_VERBOSE, "Patch schedule...");
3124     if (!ExecutePatch(ctx, schedule, PACKAGE_ACTION_PATCH))
3125     {
3126         return;
3127     }
3128 
3129     Log(LOG_LEVEL_VERBOSE, "Verify schedule...");
3130     if (!ExecuteSchedule(ctx, schedule, PACKAGE_ACTION_VERIFY))
3131     {
3132         return;
3133     }
3134 }
3135 
3136 /**
3137  * @brief Execute the full package schedule.
3138  *
3139  * Called by cf-agent only.
3140  *
3141  */
ExecuteScheduledPackages(EvalContext * ctx)3142 void ExecuteScheduledPackages(EvalContext *ctx)
3143 {
3144     if (PACKAGE_SCHEDULE)
3145     {
3146         ExecutePackageSchedule(ctx, PACKAGE_SCHEDULE);
3147     }
3148 }
3149 
3150 /** Cleanup **/
3151 
3152 /**
3153  * @brief Clean the package schedule and installed lists.
3154  *
3155  * Called by cf-agent only.  Cleans bookkeeping data.
3156  *
3157  */
CleanScheduledPackages(void)3158 void CleanScheduledPackages(void)
3159 {
3160     DeletePackageManagers(PACKAGE_SCHEDULE);
3161     PACKAGE_SCHEDULE = NULL;
3162     DeletePackageManagers(INSTALLED_PACKAGE_LISTS);
3163     INSTALLED_PACKAGE_LISTS = NULL;
3164 }
3165 
3166 /** Utils **/
3167 
GetPackageManager(PackageManager ** lists,char * mgr,PackageAction pa,PackageActionPolicy policy)3168 static PackageManager *GetPackageManager(PackageManager **lists, char *mgr,
3169                                          PackageAction pa,
3170                                          PackageActionPolicy policy)
3171 {
3172     PackageManager *np;
3173 
3174     Log(LOG_LEVEL_VERBOSE, "Looking for a package manager called '%s'", mgr);
3175 
3176     if (mgr == NULL || mgr[0] == '\0')
3177     {
3178         Log(LOG_LEVEL_ERR, "Attempted to create a package manager with no name");
3179         return NULL;
3180     }
3181 
3182     for (np = *lists; np != NULL; np = np->next)
3183     {
3184         if ((strcmp(np->manager, mgr) == 0) && (policy == np->policy))
3185         {
3186             return np;
3187         }
3188     }
3189 
3190     np = xcalloc(1, sizeof(PackageManager));
3191 
3192     np->manager = xstrdup(mgr);
3193     np->action = pa;
3194     np->policy = policy;
3195     np->next = *lists;
3196     *lists = np;
3197     return np;
3198 }
3199 
DeletePackageItems(PackageItem * pi)3200 static void DeletePackageItems(PackageItem * pi)
3201 {
3202     while (pi != NULL)
3203     {
3204         PackageItem *next = pi->next;
3205         free(pi->name);
3206         free(pi->version);
3207         free(pi->arch);
3208         PromiseDestroy(pi->pp);
3209         free(pi);
3210         pi = next;
3211     }
3212 }
3213 
DeletePackageManagers(PackageManager * np)3214 static void DeletePackageManagers(PackageManager *np)
3215 {
3216     while (np != NULL)
3217     {
3218         PackageManager *next = np->next;
3219         DeletePackageItems(np->pack_list);
3220         DeletePackageItems(np->patch_list);
3221         DeletePackageItems(np->patch_avail);
3222         free(np->manager);
3223         free(np);
3224         np = next;
3225     }
3226 }
3227 
PrefixLocalRepository(const Rlist * repositories,const char * package)3228 const char *PrefixLocalRepository(const Rlist *repositories, const char *package)
3229 {
3230     static char quotedPath[CF_MAXVARSIZE]; /* GLOBAL_R, no need to initialize */
3231     struct stat sb;
3232     char path[sizeof(quotedPath) - 2];
3233 
3234     for (const Rlist *rp = repositories; rp != NULL; rp = rp->next)
3235     {
3236         if (strlcpy(path, RlistScalarValue(rp), sizeof(path)) < sizeof(path))
3237         {
3238             AddSlash(path);
3239 
3240             if (strlcat(path, package, sizeof(path)) < sizeof(path) &&
3241                 stat(path, &sb) != -1)
3242             {
3243                 snprintf(quotedPath, sizeof(quotedPath), "\"%s\"", path);
3244                 return quotedPath;
3245             }
3246         }
3247     }
3248 
3249     return NULL;
3250 }
3251 
ExecPackageCommand(EvalContext * ctx,char * command,int verify,int setCmdClasses,const Attributes * a,const Promise * pp,PromiseResult * result)3252 bool ExecPackageCommand(EvalContext *ctx, char *command, int verify, int setCmdClasses, const Attributes *a,
3253                         const Promise *pp, PromiseResult *result)
3254 {
3255     assert(a != NULL);
3256     assert(pp != NULL); // Dereferenced by cfPS macros
3257 
3258     bool retval = true;
3259     char *cmd;
3260     FILE *pfp;
3261     int packmanRetval = 0;
3262 
3263     if ((!a->packages.package_commands_useshell) && (!IsExecutable(CommandArg0(command))))
3264     {
3265         cfPS_HELPER_1ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "The proposed package schedule command '%s' was not executable", command);
3266         *result = PromiseResultUpdate_HELPER(pp, *result, PROMISE_RESULT_FAIL);
3267         return false;
3268     }
3269 
3270     if (DONTDO)
3271     {
3272         return true;
3273     }
3274 
3275 /* Use this form to avoid limited, intermediate argument processing - long lines */
3276 
3277     if (a->packages.package_commands_useshell)
3278     {
3279         Log(LOG_LEVEL_VERBOSE, "Running %s in shell", command);
3280         if ((pfp = cf_popen_sh(command, "r")) == NULL)
3281         {
3282             cfPS_HELPER_2ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Couldn't start command '%20s'. (cf_popen_sh: %s)",
3283                  command, GetErrorStr());
3284             *result = PromiseResultUpdate_HELPER(pp, *result, PROMISE_RESULT_FAIL);
3285             return false;
3286         }
3287     }
3288     else
3289     {
3290         if ((pfp = cf_popen(command, "r", true)) == NULL)
3291         {
3292             cfPS_HELPER_2ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Couldn't start command '%20s'. (cf_popen: %s)",
3293                  command, GetErrorStr());
3294             *result = PromiseResultUpdate_HELPER(pp, *result, PROMISE_RESULT_FAIL);
3295             return false;
3296         }
3297     }
3298 
3299     Log(LOG_LEVEL_VERBOSE, "Executing %-.60s...", command);
3300 
3301 /* Look for short command summary */
3302     for (cmd = command; (*cmd != '\0') && (*cmd != ' '); cmd++)
3303     {
3304     }
3305 
3306     while (cmd > command && cmd[-1] != FILE_SEPARATOR)
3307     {
3308         cmd--;
3309     }
3310 
3311     size_t line_size = CF_BUFSIZE;
3312     char *line = xmalloc(line_size);
3313 
3314     for (;;)
3315     {
3316         ssize_t res = CfReadLine(&line, &line_size, pfp);
3317         if (res == -1)
3318         {
3319             if (!feof(pfp))
3320             {
3321                 cfPS_HELPER_2ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Unable to read output from command '%20s'. (fread: %s)",
3322                      command, GetErrorStr());
3323                 *result = PromiseResultUpdate_HELPER(pp, *result, PROMISE_RESULT_FAIL);
3324                 cf_pclose(pfp);
3325                 free(line);
3326                 return false;
3327             }
3328             else
3329             {
3330                 break;
3331             }
3332         }
3333 
3334         /* Need space for 2x buffer and null byte. */
3335         char lineSafe[res * 2 + 1];
3336         memcpy(lineSafe, line, res + 1);
3337 
3338         ssize_t replace_res =
3339             StringReplace(lineSafe, sizeof(lineSafe), "%", "%%");
3340         if (replace_res == -1)
3341         {
3342             ProgrammingError("StringReplace(): buffer overflow in %s",
3343                              __FILE__);
3344             continue;
3345         }
3346 
3347         Log(LOG_LEVEL_INFO, "Q:%20.20s ...:%s", cmd, lineSafe);
3348 
3349         if (verify && (line[0] != '\0'))
3350         {
3351             if (a->packages.package_noverify_regex)
3352             {
3353                 if (FullTextMatch(ctx, a->packages.package_noverify_regex, line))
3354                 {
3355                     cfPS_HELPER_2ARG(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_FAIL, pp, a, "Package verification error in %-.40s ... :%s", cmd, lineSafe);
3356                     *result = PromiseResultUpdate_HELPER(pp, *result, PROMISE_RESULT_FAIL);
3357                     retval = false;
3358                 }
3359             }
3360         }
3361     }
3362 
3363     free(line);
3364 
3365     packmanRetval = cf_pclose(pfp);
3366 
3367     if (verify && (a->packages.package_noverify_returncode != CF_NOINT))
3368     {
3369         if (a->packages.package_noverify_returncode == packmanRetval)
3370         {
3371             cfPS_HELPER_1ARG(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_FAIL, pp, a, "Package verification error (returned %d)", packmanRetval);
3372             *result = PromiseResultUpdate_HELPER(pp, *result, PROMISE_RESULT_FAIL);
3373             retval = false;
3374         }
3375         else
3376         {
3377             cfPS_HELPER_1ARG(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_NOOP, pp, a, "Package verification succeeded (returned %d)", packmanRetval);
3378             *result = PromiseResultUpdate_HELPER(pp, *result, PROMISE_RESULT_FAIL);
3379         }
3380     }
3381     else if (verify && (a->packages.package_noverify_regex))
3382     {
3383         if (retval)             // set status if we succeeded above
3384         {
3385             cfPS_HELPER_0ARG(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_NOOP, pp, a,
3386                  "Package verification succeeded (no match with package_noverify_regex)");
3387         }
3388     }
3389     else if (setCmdClasses)     // generic return code check
3390     {
3391         if (REPORT_THIS_PROMISE(pp))
3392         {
3393             retval = VerifyCommandRetcode(ctx, packmanRetval, a, pp, result);
3394         }
3395     }
3396 
3397     return retval;
3398 }
3399 
PrependPackageItem(EvalContext * ctx,PackageItem ** list,const char * name,const char * version,const char * arch,const Promise * pp)3400 bool PrependPackageItem(
3401     EvalContext *ctx,
3402     PackageItem ** list,
3403     const char *name,
3404     const char *version,
3405     const char *arch,
3406     const Promise *pp)
3407 {
3408     if (!list || !name[0] || !version[0] || !arch[0])
3409     {
3410         return false;
3411     }
3412 
3413     Log(LOG_LEVEL_VERBOSE,
3414         "Package (%s,%s,%s) [name,version,arch] found",
3415         name, version, arch);
3416 
3417     PackageItem *pi = xmalloc(sizeof(PackageItem));
3418 
3419     pi->next = *list;
3420     pi->name = xstrdup(name);
3421     pi->version = xstrdup(version);
3422     pi->arch = xstrdup(arch);
3423     *list = pi;
3424 
3425 /* Finally we need these for later schedule exec, once this iteration context has gone */
3426 
3427     pi->pp = DeRefCopyPromise(ctx, pp);
3428     return true;
3429 }
3430 
PackageInItemList(PackageItem * list,char * name,char * version,char * arch)3431 static bool PackageInItemList(
3432     PackageItem * list, char *name, char *version, char *arch)
3433 {
3434     if (!name[0] || !version[0] || !arch[0])
3435     {
3436         return false;
3437     }
3438 
3439     for (PackageItem *pi = list; pi != NULL; pi = pi->next)
3440     {
3441         if (strcmp(pi->name, name) == 0 &&
3442             strcmp(pi->version, version) == 0 &&
3443             strcmp(pi->arch, arch) == 0)
3444         {
3445             return true;
3446         }
3447     }
3448 
3449     return false;
3450 }
3451 
PrependPatchItem(EvalContext * ctx,PackageItem ** list,char * item,PackageItem * chklist,const char * default_arch,const Attributes * a,const Promise * pp)3452 static bool PrependPatchItem(
3453     EvalContext *ctx,
3454     PackageItem ** list,
3455     char *item,
3456     PackageItem * chklist,
3457     const char *default_arch,
3458     const Attributes *a,
3459     const Promise *pp)
3460 {
3461     assert(a != NULL);
3462     char name[CF_MAXVARSIZE];
3463     char arch[CF_MAXVARSIZE];
3464     char version[CF_MAXVARSIZE];
3465     char vbuff[CF_MAXVARSIZE];
3466 
3467     strlcpy(vbuff, ExtractFirstReference(a->packages.package_patch_name_regex, item), CF_MAXVARSIZE);
3468     sscanf(vbuff, "%s", name);  /* trim */
3469     strlcpy(vbuff, ExtractFirstReference(a->packages.package_patch_version_regex, item), CF_MAXVARSIZE);
3470     sscanf(vbuff, "%s", version);       /* trim */
3471 
3472     if (a->packages.package_patch_arch_regex)
3473     {
3474         strlcpy(vbuff, ExtractFirstReference(a->packages.package_patch_arch_regex, item), CF_MAXVARSIZE );
3475         sscanf(vbuff, "%s", arch);      /* trim */
3476     }
3477     else
3478     {
3479         strlcpy(arch, default_arch, CF_MAXVARSIZE );
3480     }
3481 
3482     if ((strcmp(name, "CF_NOMATCH") == 0) || (strcmp(version, "CF_NOMATCH") == 0) || (strcmp(arch, "CF_NOMATCH") == 0))
3483     {
3484         return false;
3485     }
3486 
3487     Log(LOG_LEVEL_DEBUG, "PrependPatchItem: Patch line '%s', with name '%s', version '%s', arch '%s'", item, name, version, arch);
3488 
3489     if (PackageInItemList(chklist, name, version, arch))
3490     {
3491         Log(LOG_LEVEL_VERBOSE, "Patch for (%s,%s,%s) [name,version,arch] found, but it appears to be installed already", name, version,
3492               arch);
3493         return false;
3494     }
3495 
3496     return PrependPackageItem(ctx, list, name, version, arch, pp);
3497 }
3498 
PrependMultiLinePackageItem(EvalContext * ctx,PackageItem ** list,char * item,int reset,const char * default_arch,const Attributes * a,const Promise * pp)3499 static bool PrependMultiLinePackageItem(
3500     EvalContext *ctx,
3501     PackageItem ** list,
3502     char *item,
3503     int reset,
3504     const char *default_arch,
3505     const Attributes *a,
3506     const Promise *pp)
3507 {
3508     assert(a != NULL);
3509     static char name[CF_MAXVARSIZE] = ""; /* GLOBAL_X */
3510     static char arch[CF_MAXVARSIZE] = ""; /* GLOBAL_X */
3511     static char version[CF_MAXVARSIZE] = ""; /* GLOBAL_X */
3512     static char vbuff[CF_MAXVARSIZE] = ""; /* GLOBAL_X */
3513 
3514     if (reset)
3515     {
3516         if ((strcmp(name, "CF_NOMATCH") == 0) || (strcmp(version, "CF_NOMATCH") == 0))
3517         {
3518             return false;
3519         }
3520 
3521         if ((strcmp(name, "") != 0) || (strcmp(version, "") != 0))
3522         {
3523             Log(LOG_LEVEL_DEBUG, "PrependMultiLinePackageItem: Extracted package name '%s', version '%s', arch '%s'", name, version, arch);
3524             PrependPackageItem(ctx, list, name, version, arch, pp);
3525         }
3526 
3527         strcpy(name, "CF_NOMATCH");
3528         strcpy(version, "CF_NOMATCH");
3529         strcpy(arch, default_arch);
3530     }
3531 
3532     if (FullTextMatch(ctx, a->packages.package_list_name_regex, item))
3533     {
3534         strlcpy(vbuff, ExtractFirstReference(a->packages.package_list_name_regex, item), CF_MAXVARSIZE);
3535         sscanf(vbuff, "%s", name);      /* trim */
3536     }
3537 
3538     if (FullTextMatch(ctx, a->packages.package_list_version_regex, item))
3539     {
3540         strlcpy(vbuff, ExtractFirstReference(a->packages.package_list_version_regex, item), CF_MAXVARSIZE );
3541         sscanf(vbuff, "%s", version);   /* trim */
3542     }
3543 
3544     if ((a->packages.package_list_arch_regex) && (FullTextMatch(ctx, a->packages.package_list_arch_regex, item)))
3545     {
3546         if (a->packages.package_list_arch_regex)
3547         {
3548             strlcpy(vbuff, ExtractFirstReference(a->packages.package_list_arch_regex, item), CF_MAXVARSIZE);
3549             sscanf(vbuff, "%s", arch);  /* trim */
3550         }
3551     }
3552 
3553     return true;
3554 }
3555 
PrependListPackageItem(EvalContext * ctx,PackageItem ** list,char * item,const char * default_arch,const Attributes * a,const Promise * pp)3556 static bool PrependListPackageItem(
3557     EvalContext *ctx,
3558     PackageItem **list,
3559     char *item,
3560     const char *default_arch,
3561     const Attributes *a,
3562     const Promise *pp)
3563 {
3564     assert(a != NULL);
3565     char name[CF_MAXVARSIZE];
3566     char arch[CF_MAXVARSIZE];
3567     char version[CF_MAXVARSIZE];
3568     char vbuff[CF_MAXVARSIZE];
3569 
3570     strlcpy(vbuff, ExtractFirstReference(a->packages.package_list_name_regex, item), CF_MAXVARSIZE);
3571     sscanf(vbuff, "%s", name);  /* trim */
3572 
3573     strlcpy(vbuff, ExtractFirstReference(a->packages.package_list_version_regex, item), CF_MAXVARSIZE);
3574     sscanf(vbuff, "%s", version);       /* trim */
3575 
3576     if (a->packages.package_list_arch_regex)
3577     {
3578         strlcpy(vbuff, ExtractFirstReference(a->packages.package_list_arch_regex, item), CF_MAXVARSIZE);
3579         sscanf(vbuff, "%s", arch);      /* trim */
3580     }
3581     else
3582     {
3583         strlcpy(arch, default_arch, CF_MAXVARSIZE);
3584     }
3585 
3586     if ((strcmp(name, "CF_NOMATCH") == 0) || (strcmp(version, "CF_NOMATCH") == 0) || (strcmp(arch, "CF_NOMATCH") == 0))
3587     {
3588         return false;
3589     }
3590 
3591     Log(LOG_LEVEL_DEBUG, "PrependListPackageItem: Package line '%s', name '%s', version '%s', arch '%s'", item, name, version, arch);
3592 
3593     return PrependPackageItem(ctx, list, name, version, arch, pp);
3594 }
3595 
GetDefaultArch(const char * command)3596 static char *GetDefaultArch(const char *command)
3597 {
3598     if (command == NULL)
3599     {
3600         return xstrdup("default");
3601     }
3602 
3603     Log(LOG_LEVEL_VERBOSE, "Obtaining default architecture for package manager '%s'", command);
3604 
3605     FILE *fp = cf_popen_sh(command, "r");
3606     if (fp == NULL)
3607     {
3608         return NULL;
3609     }
3610 
3611     size_t arch_size = CF_SMALLBUF;
3612     char *arch = xmalloc(arch_size);
3613 
3614     ssize_t res = CfReadLine(&arch, &arch_size, fp);
3615     if (res == -1)
3616     {
3617         cf_pclose(fp);
3618         free(arch);
3619         return NULL;
3620     }
3621 
3622     Log(LOG_LEVEL_VERBOSE, "Default architecture for package manager is '%s'", arch);
3623 
3624     cf_pclose(fp);
3625     return arch;
3626 }
3627