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