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