cfengine  3.15.4
About: CFEngine is a configuration management system for configuring and maintaining Unix-like computers (using an own high level policy language). Community version.
  Fossies Dox: cfengine-3.15.4.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

verify_packages.c
Go to the documentation of this file.
1 /*
2  Copyright 2019 Northern.tech AS
3 
4  This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5 
6  This program is free software; you can redistribute it and/or modify it
7  under the terms of the GNU General Public License as published by the
8  Free Software Foundation; version 3.
9 
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program; if not, write to the Free Software
17  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18 
19  To the extent this program is licensed as part of the Enterprise
20  versions of CFEngine, the applicable Commercial Open Source License
21  (COSL) may apply to this file if you as a licensee so wish it. See
22  included file COSL.txt.
23 */
24 
25 #include <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>
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 {
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 
125 static void DeletePackageManagers(PackageManager *morituri);
126 
127 static const char *PrefixLocalRepository(const Rlist *repositories, const char *package);
128 
130 
132 {
133  Log(LOG_LEVEL_VERBOSE, "Patch reporting feature is only available in the enterprise version");
134 }
135 
136 /*****************************************************************************/
137 
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. */
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  {
159  }
160  else if (!new_packages->is_empty) /* new packages promise */
161  {
162  if (new_packages->package_policy == NEW_PACKAGE_ACTION_NONE)
163  {
165  }
167  }
168  else /* old packages promise */
169  {
170  //TODO:
171  if (!packages->has_package_method)
172  {
174  }
176  }
177 }
178 
180 {
181  assert(pp != NULL); // Dereferenced in cfPS macros
182 
184  char *promise_log_message = NULL;
185  LogLevel level;
186 
187  Attributes a = GetPackageAttributes(ctx, pp);
188  PackagePromiseType package_promise_type =
190 
191  switch (package_promise_type)
192  {
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;
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;
225  "New package promise failed sanity check.");
226  break;
229  "Old package promise failed sanity check.");
230  break;
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 */
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];
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 
291  {
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 
309  if (package_lock.g_lock.lock == NULL)
310  {
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 
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 
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  {
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  {
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 
420  {
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  {
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  {
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  {
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  {
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  {
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 
500  {
501  if (!pkgs->package_verify_command)
502  {
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  {
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 
516  {
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  {
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  {
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  {
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  {
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  {
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  {
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  {
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  {
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  {
583  {
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  {
594  "!! Dependency conflict: package_verify_command is not used, but package_noverify_regex is defined.");
595  return false;
596  }
598  {
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 */
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 
636  {
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",
652 
653  if (stat(update_timestamp_file, &sb) != -1)
654  {
656  {
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 
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)",
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)",
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)",
745  cf_pclose(fin);
746  free(buf);
747  return false;
748  }
749  else
750  {
751  break;
752  }
753  }
754 
756  {
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  {
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 
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 */
805 static void ReportSoftware(PackageManager *list)
806 {
807  PackageManager *mp = NULL;
808  PackageItem *pi;
809  char name[CF_BUFSIZE];
810 
812 
813  FILE *fout = safe_fopen(name, "w");
814  if (fout == NULL)
815  {
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);
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 */
857 static void InvalidateSoftwareCache(void)
858 {
859  char name[CF_BUFSIZE];
860  struct utimbuf epoch = { 0, 0 };
861 
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 */
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 
906 
907  if (stat(name, &sb) == -1)
908  {
909  return NULL;
910  }
911 
913  {
915  }
916 
917  if (now - sb.st_mtime < horizon * 60)
918  {
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 */
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 
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 
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 
1061 
1062 /* Now get available updates */
1063 
1065  {
1066  Log(LOG_LEVEL_VERBOSE, "Reading patches from '%s'", CommandArg0(a->packages.package_patch_list_command));
1067 
1069  {
1070  Log(LOG_LEVEL_ERR, "The proposed patch list command '%s' was not executable",
1072  return false;
1073  }
1074 
1075  FILE *fin;
1076 
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)",
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)",
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)",
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
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 
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 */
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 */
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);
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  {
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;
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 */
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";
1315  return "reinstalling";
1316  case PACKAGE_ACTION_UPDATE:
1317  return "updating";
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 */
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  {
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 */
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  {
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 */
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 
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();
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 
1526  {
1528  strlcpy(id, BufferData(expanded), CF_EXPANDSIZE);
1529  }
1530  else if (a->packages.package_name_convention)
1531  {
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  {
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 
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 
1593  switch (policy)
1594  {
1595  case PACKAGE_ACTION_ADD:
1596 
1597  if (installed == 0)
1598  {
1600  {
1601  Log(LOG_LEVEL_VERBOSE, "Package method specifies a file repository");
1602 
1603  {
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 
1611  EvalContextVariablePut(ctx, ref_arch, arch, CF_DATA_TYPE_STRING, "source=promise");
1612 
1613  BufferClear(expanded);
1615  {
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,
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 
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
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);
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,
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 
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,
1747  PACKAGE_ACTION_DELETE, id, "any", "any", pp));
1748  }
1749 
1750  result = PromiseResultUpdate_HELPER(pp, result,
1752  PACKAGE_ACTION_ADD, id, "any", "any", pp));
1753  }
1754  else
1755  {
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 
1771  {
1772  Log(LOG_LEVEL_VERBOSE, "Package method specifies a file repository");
1773 
1774  {
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 
1782  EvalContextVariablePut(ctx, ref_arch, arch, CF_DATA_TYPE_STRING, "source=promise");
1783 
1784  BufferClear(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  {
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  {
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  {
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;
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);
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  }
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,
1897  PACKAGE_ACTION_DELETE, id_del, "any", "any", pp));
1898 
1899  result = PromiseResultUpdate_HELPER(pp, result,
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,
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,
1926  PACKAGE_ACTION_PATCH, id, "any", "any", pp));
1927  }
1928  else
1929  {
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,
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 */
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);
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,
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 */
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",
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 */
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 */
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 
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 
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 */
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 
2269 
2270  /* Horrible */
2271  Attributes a2 = *a;
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 */
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;
2355 
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);
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 */
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 */
2460 {
2461  assert(a != NULL);
2462  assert(pp != NULL);
2463 
2464  const char *package = pp->promiser;
2465  const Packages *const pkgs = &(a->packages);
2466 
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 */
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  {
2604 
2605  if (size > estimated_size)
2606  {
2607  estimated_size = size + CF_MAXVARSIZE;
2608  }
2609  break;
2610 
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 
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 
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 
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 
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");
2707 
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 
2721  }
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 
2763  {
2764  bool ok = ExecPackageCommand(ctx, command_string, verify, true, &a, ppi, &result);
2765 
2767  {
2768  Log(LOG_LEVEL_DEBUG, "ExecuteSchedule: Ignoring outcome for special package '%s'", pi->name);
2769  }
2770  else if (ok)
2771  {
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 
2782  }
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 
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 
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  {
2831  {
2832  Log(LOG_LEVEL_DEBUG, "ExecuteSchedule: Ignoring outcome for special package '%s'", pi->name);
2833  }
2834  else if (ok)
2835  {
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 
2848  }
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  {
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 */
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  {
2926  if (size > estimated_size)
2927  {
2928  estimated_size = size;
2929  }
2930  break;
2931 
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 
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 
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 
2987  }
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  {
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 
3009  {
3010  bool ok = ExecPackageCommand(ctx, command_string, false, true, &a, pp, &result);
3011 
3013  {
3014  Log(LOG_LEVEL_DEBUG, "ExecutePatch: Ignoring outcome for special package '%s'", pi->name);
3015  }
3016  else if (ok)
3017  {
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 
3028  }
3030  EvalContextLogPromiseIterationOutcome(ctx, pp, result);
3031 
3032  *offset = '\0';
3033  }
3034 
3035  break;
3036 
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 
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  {
3056  {
3057  Log(LOG_LEVEL_DEBUG, "ExecutePatch: Ignoring outcome for special package '%s'", pi->name);
3058  }
3059  else if (ok)
3060  {
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 
3073  }
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 */
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 */
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  */
3155 {
3156  if (PACKAGE_SCHEDULE)
3157  {
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  */
3171 {
3176 }
3177 
3178 /** Utils **/
3179 
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 
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 
3227 {
3228  while (np != NULL)
3229  {
3230  PackageManager *next = np->next;
3234  free(np->manager);
3235  free(np);
3236  np = next;
3237  }
3238 }
3239 
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 
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 
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  {
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  {
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 
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 
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 
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 
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 
3480  sscanf(vbuff, "%s", name); /* trim */
3482  sscanf(vbuff, "%s", version); /* trim */
3483 
3485  {
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 
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  {
3547  sscanf(vbuff, "%s", name); /* trim */
3548  }
3549 
3551  {
3553  sscanf(vbuff, "%s", version); /* trim */
3554  }
3555 
3557  {
3559  {
3561  sscanf(vbuff, "%s", arch); /* trim */
3562  }
3563  }
3564 
3565  return true;
3566 }
3567 
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 
3583  sscanf(vbuff, "%s", name); /* trim */
3584 
3586  sscanf(vbuff, "%s", version); /* trim */
3587 
3589  {
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 
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 }
void * xmalloc(size_t size)
Definition: alloc-mini.c:46
void * xcalloc(size_t nmemb, size_t size)
Definition: alloc-mini.c:51
char * xstrdup(const char *str)
Definition: alloc-mini.c:56
Attributes GetPackageAttributes(const EvalContext *ctx, const Promise *pp)
Definition: attributes.c:157
void BufferDestroy(Buffer *buffer)
Destroys a buffer and frees the memory associated with it.
Definition: buffer.c:72
Buffer * BufferNew(void)
Buffer initialization routine.
Definition: buffer.c:48
const char * BufferData(const Buffer *buffer)
Provides a pointer to the internal data.
Definition: buffer.c:470
void BufferClear(Buffer *buffer)
Clears the buffer.
Definition: buffer.c:457
ENTERPRISE_VOID_FUNC_1ARG_DEFINE_STUB(void, Nova_TrackExecution, ARG_UNUSED const char *, input_file)
#define ARG_UNUSED
Definition: cf-net.c:47
@ PACKAGE_VERSION_COMPARATOR_GE
Definition: cf3.defs.h:796
@ PACKAGE_VERSION_COMPARATOR_LT
Definition: cf3.defs.h:795
@ PACKAGE_VERSION_COMPARATOR_NONE
Definition: cf3.defs.h:798
@ PACKAGE_VERSION_COMPARATOR_LE
Definition: cf3.defs.h:797
@ PACKAGE_VERSION_COMPARATOR_EQ
Definition: cf3.defs.h:792
@ NEW_PACKAGE_ACTION_NONE
Definition: cf3.defs.h:787
PackageAction
Definition: cf3.defs.h:772
@ PACKAGE_ACTION_VERIFY
Definition: cf3.defs.h:779
@ PACKAGE_ACTION_DELETE
Definition: cf3.defs.h:774
@ PACKAGE_ACTION_NONE
Definition: cf3.defs.h:780
@ PACKAGE_ACTION_PATCH
Definition: cf3.defs.h:778
@ PACKAGE_ACTION_ADD
Definition: cf3.defs.h:773
@ PACKAGE_ACTION_REINSTALL
Definition: cf3.defs.h:775
@ PACKAGE_ACTION_ADDUPDATE
Definition: cf3.defs.h:777
@ PACKAGE_ACTION_UPDATE
Definition: cf3.defs.h:776
#define PACKAGES_CONTEXT_ANYVER
Definition: cf3.defs.h:598
PromiseResult
Definition: cf3.defs.h:122
@ PROMISE_RESULT_CHANGE
Definition: cf3.defs.h:125
@ PROMISE_RESULT_NOOP
Definition: cf3.defs.h:124
@ PROMISE_RESULT_WARN
Definition: cf3.defs.h:126
@ PROMISE_RESULT_SKIPPED
Definition: cf3.defs.h:123
@ PROMISE_RESULT_FAIL
Definition: cf3.defs.h:127
PackageActionPolicy
Definition: cf3.defs.h:802
@ PACKAGE_ACTION_POLICY_NONE
Definition: cf3.defs.h:805
@ PACKAGE_ACTION_POLICY_BULK
Definition: cf3.defs.h:804
@ PACKAGE_ACTION_POLICY_INDIVIDUAL
Definition: cf3.defs.h:803
@ cfa_warn
Definition: cf3.defs.h:719
@ cfa_fix
Definition: cf3.defs.h:718
#define PACKAGES_CONTEXT
Definition: cf3.defs.h:597
@ CF_DATA_TYPE_STRING
Definition: cf3.defs.h:369
#define CF_NOINT
Definition: cf3.defs.h:339
bool DONTDO
Definition: cf3globals.c:55
time_t CFSTARTTIME
Definition: cf3globals.c:99
char VUQNAME[]
Definition: cf3globals.c:59
void free(void *)
const char * CommandArg0(const char *execstr)
Definition: conversion.c:882
void CsvWriterField(CsvWriter *csvw, const char *str)
Definition: csv_writer.c:60
CsvWriter * CsvWriterOpen(Writer *w)
Definition: csv_writer.c:53
void CsvWriterClose(CsvWriter *csvw)
Definition: csv_writer.c:106
void CsvWriterNewRecord(CsvWriter *csvw)
Definition: csv_writer.c:96
#define CF_BUFSIZE
Definition: definitions.h:50
#define CF_EXPANDSIZE
Definition: definitions.h:51
#define CF_MAXVARSIZE
Definition: definitions.h:36
#define CF_SMALLBUF
Definition: definitions.h:49
const struct dirent * DirRead(Dir *dir)
Definition: unix_dir.c:92
void DirClose(Dir *dir)
Definition: unix_dir.c:118
Dir * DirOpen(const char *dirname)
Definition: unix_dir.c:41
void EvalContextLogPromiseIterationOutcome(EvalContext *ctx, const Promise *pp, PromiseResult result)
void EvalContextStackPushPromiseFrame(EvalContext *ctx, const Promise *owner)
void cfPS(EvalContext *ctx, LogLevel level, PromiseResult status, const Promise *pp, const Attributes *attr, const char *fmt,...)
Promise * EvalContextStackPushPromiseIterationFrame(EvalContext *ctx, const PromiseIterator *iter_ctx)
const void * EvalContextVariableGet(const EvalContext *ctx, const VarRef *ref, DataType *type_out)
bool EvalContextVariableRemove(const EvalContext *ctx, const VarRef *ref)
void EvalContextStackPopFrame(EvalContext *ctx)
bool EvalContextVariablePut(EvalContext *ctx, const VarRef *ref, const void *value, DataType type, const char *tags)
bool IsExecutable(const char *file)
Definition: unix.c:130
char * ExpandScalar(const EvalContext *ctx, const char *ns, const char *scope, const char *string, Buffer *out)
Definition: expand.c:516
int safe_chdir(const char *path)
Definition: file_lib.c:903
ssize_t CfReadLine(char **buff, size_t *size, FILE *fp)
Works exactly like posix 'getline', EXCEPT it does not include carriage return at the end.
Definition: file_lib.c:1476
FILE * safe_fopen(const char *const path, const char *const mode)
Definition: file_lib.c:812
#define FILE_SEPARATOR
Definition: file_lib.h:102
int CompareCSVName(const char *s1, const char *s2)
Definition: files_names.c:514
void AddSlash(char *str)
Definition: files_names.c:230
const char * ReadLastNode(const char *str)
Definition: files_names.c:539
const char * RealPackageManager(const char *manager)
Definition: files_names.c:813
const char * GetSoftwareCacheFilename(char *buffer)
Definition: files_names.c:798
int errno
#define NULL
Definition: getopt1.c:56
const char * GetStateDir(void)
Definition: known_dirs.c:186
void YieldCurrentLock(CfLock lock)
Definition: locks.c:948
CfLock AcquireLock(EvalContext *ctx, const char *operand, const char *host, time_t now, int ifelapsed, int expireafter, const Promise *pp, bool ignoreProcesses)
Definition: locks.c:789
LogLevel
Definition: log.h:30
const char * GetErrorStr(void)
Definition: logging.c:275
void Log(LogLevel level, const char *fmt,...)
Definition: logging.c:409
@ LOG_LEVEL_ERR
Definition: logging.h:42
@ LOG_LEVEL_DEBUG
Definition: logging.h:47
@ LOG_LEVEL_WARNING
Definition: logging.h:43
@ LOG_LEVEL_VERBOSE
Definition: logging.h:46
@ LOG_LEVEL_INFO
Definition: logging.h:45
bool FullTextMatch(EvalContext *ctx, const char *regexp, const char *teststring)
Definition: match_scope.c:87
char * ExtractFirstReference(const char *regexp, const char *teststring)
Definition: matching.c:69
void EscapeSpecialChars(const char *str, char *strEsc, int strEscSz, char *noEscSeq, char *noEscList)
Definition: matching.c:264
#define ProgrammingError(...)
Definition: misc_lib.h:33
#define UnexpectedError(...)
Definition: misc_lib.h:38
void PromiseBanner(EvalContext *ctx, const Promise *pp)
Definition: ornaments.c:127
PackagePromiseGlobalLock AcquireGlobalPackagePromiseLock(EvalContext *ctx)
void UpdatePackagesCache(EvalContext *ctx, bool force_update)
void YieldGlobalPackagePromiseLock(PackagePromiseGlobalLock lock)
int cf_pclose(FILE *pp)
Definition: pipes_unix.c:812
FILE * cf_popen(const char *command, const char *type, bool capture_stderr)
Definition: pipes_unix.c:332
FILE * cf_popen_sh(const char *command, const char *type)
Definition: pipes_unix.c:560
#define dirent
Definition: platform.h:160
#define PATH_MAX
Definition: platform.h:176
void PromiseDestroy(Promise *pp)
Definition: policy.c:1471
Promise * DeRefCopyPromise(EvalContext *ctx, const Promise *pp)
Definition: promises.c:205
bool VerifyCommandRetcode(EvalContext *ctx, int retcode, const Attributes *a, const Promise *pp, PromiseResult *result)
Definition: retcode.c:32
char * RlistScalarValue(const Rlist *rlist)
Definition: rlist.c:83
bool StringEqual(const char *const a, const char *const b)
Definition: string_lib.c:256
ssize_t StringReplace(char *buf, size_t buf_size, const char *find, const char *replace)
Definition: string_lib.c:868
size_t strlcat(char *dst, const char *src, size_t siz)
Definition: strlcat.c:36
size_t strlcpy(char *dst, const char *src, size_t siz)
Definition: strlcpy.c:34
NewPackages new_packages
Definition: cf3.defs.h:1545
Packages packages
Definition: cf3.defs.h:1544
TransactionContext transaction
Definition: cf3.defs.h:1567
Definition: buffer.h:50
char * lock
Definition: cf3.defs.h:895
Definition: unix_dir.c:33
bool is_empty
Definition: cf3.defs.h:1389
NewPackageAction package_policy
Definition: cf3.defs.h:1382
PackageItem * next
Definition: cf3.defs.h:1006
char * arch
Definition: cf3.defs.h:1004
char * name
Definition: cf3.defs.h:1002
Promise * pp
Definition: cf3.defs.h:1005
char * version
Definition: cf3.defs.h:1003
PackageActionPolicy policy
Definition: cf3.defs.h:991
PackageItem * pack_list
Definition: cf3.defs.h:992
char * manager
Definition: cf3.defs.h:989
PackageAction action
Definition: cf3.defs.h:990
PackageItem * patch_list
Definition: cf3.defs.h:993
PackageItem * patch_avail
Definition: cf3.defs.h:994
PackageManager * next
Definition: cf3.defs.h:995
PackageActionPolicy package_changes
Definition: cf3.defs.h:1311
int package_noverify_returncode
Definition: cf3.defs.h:1351
char * package_noverify_regex
Definition: cf3.defs.h:1340
char * package_delete_command
Definition: cf3.defs.h:1336
char * package_version
Definition: cf3.defs.h:1308
char * package_patch_name_regex
Definition: cf3.defs.h:1323
char * package_arch_regex
Definition: cf3.defs.h:1332
char * package_default_arch_command
Definition: cf3.defs.h:1314
char * package_verify_command
Definition: cf3.defs.h:1339
PackageAction package_policy
Definition: cf3.defs.h:1307
char * package_version_regex
Definition: cf3.defs.h:1330
int package_list_update_ifelapsed
Definition: cf3.defs.h:1328
bool has_package_method
Definition: cf3.defs.h:1353
char * package_patch_arch_regex
Definition: cf3.defs.h:1324
char * package_list_arch_regex
Definition: cf3.defs.h:1319
char * package_delete_convention
Definition: cf3.defs.h:1342
bool package_commands_useshell
Definition: cf3.defs.h:1344
char * package_patch_command
Definition: cf3.defs.h:1338
Rlist * package_file_repositories
Definition: cf3.defs.h:1312
char * package_name_convention
Definition: cf3.defs.h:1341
char * package_name_regex
Definition: cf3.defs.h:1331
char * package_installed_regex
Definition: cf3.defs.h:1333
char * package_patch_version_regex
Definition: cf3.defs.h:1322
char * package_patch_installed_regex
Definition: cf3.defs.h:1325
char * package_update_command
Definition: cf3.defs.h:1337
PackageVersionComparator package_select
Definition: cf3.defs.h:1310
char * package_list_command
Definition: cf3.defs.h:1316
char * package_add_command
Definition: cf3.defs.h:1335
char * package_patch_list_command
Definition: cf3.defs.h:1320
Rlist * package_architectures
Definition: cf3.defs.h:1309
bool is_empty
Definition: cf3.defs.h:1354
char * package_list_version_regex
Definition: cf3.defs.h:1317
char * package_multiline_start
Definition: cf3.defs.h:1346
char * package_list_update_command
Definition: cf3.defs.h:1327
char * package_list_name_regex
Definition: cf3.defs.h:1318
char * promiser
Definition: policy.h:115
Definition: rlist.h:35
Rlist * next
Definition: rlist.h:37
enum cfopaction action
Definition: cf3.defs.h:927
Definition: writer.c:45
VarRef * VarRefParseFromScope(const char *var_ref_string, const char *scope)
void VarRefDestroy(VarRef *ref)
VersionCmpResult CompareVersions(EvalContext *ctx, const char *v1, const char *v2, const Attributes *a, const Promise *pp, PromiseResult *result)
Definition: vercmp.c:151
const char * PackageVersionComparatorToString(const PackageVersionComparator pvc)
Definition: vercmp.c:202
VersionCmpResult
Definition: vercmp.h:29
@ VERCMP_ERROR
Definition: vercmp.h:30
@ VERCMP_MATCH
Definition: vercmp.h:32
@ VERCMP_NO_MATCH
Definition: vercmp.h:31
PromiseResult HandleNewPackagePromiseType(EvalContext *ctx, const Promise *pp, const Attributes *a, char **promise_log_msg, LogLevel *log_lvl)
static const char * PrefixLocalRepository(const Rlist *repositories, const char *package)
static VersionCmpResult PatchMatch(EvalContext *ctx, const char *n, const char *v, const char *a, const Attributes *attr, const Promise *pp, const char *mode, PromiseResult *result)
Finds a specific package (n,v,a) [name, version, architecture] as specified by Attributes attr.
static bool ExecutePatch(EvalContext *ctx, const PackageManager *schedule, PackageAction action)
Central dispatcher for scheduled patch operations.
static PackagePromiseType GetPackagePromiseVersion(const Packages *packages, const NewPackages *new_packages)
static PromiseResult VerifyPromisedPackage(EvalContext *ctx, const Attributes *a, const Promise *pp)
Verifies a promised package operation as defined by a and pp.
static PromiseResult CheckPackageState(EvalContext *ctx, const Attributes *a, const Promise *pp, const char *name, const char *version, const char *arch, bool no_version)
Checks the state of a specific package (name,version,arch) as specified by Attributes a.
void CleanScheduledPackages(void)
Clean the package schedule and installed lists.
static bool PackageListInstalledFromCommand(EvalContext *ctx, PackageItem **installed_list, const char *default_arch, const Attributes *a, const Promise *pp, PromiseResult *result)
Generates the list of installed packages.
static bool ExecuteSchedule(EvalContext *ctx, const PackageManager *schedule, PackageAction action)
Central dispatcher for scheduled operations.
#define cfPS_HELPER_2ARG(__ctx, __log_level, __result, __pp, __a, __str, __arg1, __arg2)
static bool VerifyInstalledPackages(EvalContext *ctx, PackageManager **alllists, const char *default_arch, const Attributes *a, const Promise *pp, PromiseResult *result)
Verifies installed packages for a single Promise.
#define PromiseResultUpdate_HELPER(__pp, __prior, __evidence)
#define cfPS_HELPER_3ARG(__ctx, __log_level, __result, __pp, __a, __str, __arg1, __arg2, __arg3)
int FindLargestVersionAvail(EvalContext *ctx, char *matchName, char *matchVers, const char *refAnyVer, const char *ver, Rlist *repositories, const Attributes *a, const Promise *pp, PromiseResult *result)
Finds the largest version of a package available in a file repository.
static bool PrependPatchItem(EvalContext *ctx, PackageItem **list, char *item, PackageItem *chklist, const char *default_arch, const Attributes *a, const Promise *pp)
#define REPORT_THIS_PROMISE(__pp)
static PromiseResult VerifyPromisedPatch(EvalContext *ctx, const Attributes *a, const Promise *pp)
Verifies a promised patch operation as defined by a and pp.
static VersionCmpResult PackageMatch(EvalContext *ctx, const char *n, const char *v, const char *a, const Attributes *attr, const Promise *pp, const char *mode, PromiseResult *result)
Finds a specific package (n,v,a) [name, version, architecture] as specified by Attributes attr.
static bool WillSchedulePackageOperation(EvalContext *ctx, const Attributes *a, const Promise *pp, int matches, int installed)
Check if the operation should be scheduled based on the package policy, if the package matches,...
PackagePromiseType
@ PACKAGE_PROMISE_TYPE_OLD
@ PACKAGE_PROMISE_TYPE_OLD_ERROR
@ PACKAGE_PROMISE_TYPE_NEW_ERROR
@ PACKAGE_PROMISE_TYPE_NEW
@ PACKAGE_PROMISE_TYPE_MIXED
static const char * PackageAction2String(PackageAction pa)
Returns string version of a PackageAction.
#define PACKAGE_IGNORED_CFE_INTERNAL
static PackageItem * GetCachedPackageList(EvalContext *ctx, PackageManager *manager, const char *default_arch, const Attributes *a, const Promise *pp)
Gets the cached list of installed packages from file.
static bool PrependMultiLinePackageItem(EvalContext *ctx, PackageItem **list, char *item, int reset, const char *default_arch, const Attributes *a, const Promise *pp)
static void ReportSoftware(PackageManager *list)
Writes the software inventory.
PromiseResult VerifyPackagesPromise(EvalContext *ctx, const Promise *pp)
static PackageManager * GetPackageManager(PackageManager **lists, char *mgr, PackageAction pa, PackageActionPolicy x)
#define cfPS_HELPER_1ARG(__ctx, __log_level, __result, __pp, __a, __str, __arg1)
PackageManager * PACKAGE_SCHEDULE
static bool PrependListPackageItem(EvalContext *ctx, PackageItem **list, char *item, const char *default_arch, const Attributes *a, const Promise *pp)
#define cfPS_HELPER_0ARG(__ctx, __log_level, __result, __pp, __a, __str)
static void ExecutePackageSchedule(EvalContext *ctx, PackageManager *schedule)
Ordering manager for scheduled package operations.
static PromiseResult AddPackageToSchedule(EvalContext *ctx, const Attributes *a, char *mgr, PackageAction pa, const char *name, const char *version, const char *arch, const Promise *pp)
Adds a specific package (name,version,arch) as specified by Attributes a to the scheduled operations.
static void InvalidateSoftwareCache(void)
Invalidates the software inventory.
static bool PackageInItemList(PackageItem *list, char *name, char *version, char *arch)
static PromiseResult AddPatchToSchedule(EvalContext *ctx, const Attributes *a, char *mgr, PackageAction pa, const char *name, const char *version, const char *arch, const Promise *pp)
Adds a specific patch (name,version,arch) as specified by Attributes a to the scheduled operations.
bool PrependPackageItem(EvalContext *ctx, PackageItem **list, const char *name, const char *version, const char *arch, const Promise *pp)
static PromiseResult HandleOldPackagePromiseType(EvalContext *ctx, const Promise *pp, const Attributes *a)
Executes single packages promise.
VersionCmpResult 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)
Compare a PackageItem to a specific package (n,v,arch) as specified by Attributes a.
void ReportPatches(PackageManager *list)
#define PACKAGE_LIST_COMMAND_WINAPI
static PromiseResult 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)
Schedules a package operation based on the action, package state, and everything else.
PackageManager * INSTALLED_PACKAGE_LISTS
static char * GetDefaultArch(const char *command)
void ExecuteScheduledPackages(EvalContext *ctx)
Execute the full package schedule.
static bool PackageSanityCheck(EvalContext *ctx, const Attributes *a, const Promise *pp)
Pre-check of promise contents.
static bool ExecPackageCommand(EvalContext *ctx, char *command, int verify, int setCmdClasses, const Attributes *a, const Promise *pp, PromiseResult *result)
static void DeletePackageItems(PackageItem *pi)
static bool IsNewerThanInstalled(EvalContext *ctx, const char *n, const char *v, const char *a, char *instV, char *instA, const Attributes *attr, const Promise *pp, PromiseResult *result)
Returns true if a package (n,v,a) is installed and v is larger than the installed version.
static void DeletePackageManagers(PackageManager *morituri)
Writer * FileWriter(FILE *file)
Definition: writer.c:56
void WriterClose(Writer *writer)
Definition: writer.c:242