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