projects/aports·blob v2.8.1 · refs

685 lines · 18.0 KiB

1/* commit.c - Alpine Package Keeper (APK)
2 * Apply solver calculated changes to database.
3 *
4 * Copyright (C) 2008-2013 Timo Teräs <[email protected]>
5 * All rights reserved.
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License version 2 as published
9 * by the Free Software Foundation. See http://www.gnu.org/ for details.
10 */
11
12#include <limits.h>
13#include <stdint.h>
14#include <unistd.h>
15#include "apk_defines.h"
16#include "apk_database.h"
17#include "apk_package.h"
18#include "apk_solver.h"
19
20#include "apk_print.h"
21
22static inline int pkg_available(struct apk_database *db, struct apk_package *pkg)
23{
24 if (pkg->repos & db->available_repos)
25 return TRUE;
26 return FALSE;
27}
28
29static int print_change(struct apk_database *db, struct apk_change *change,
30 int cur, int total)
31{
32 struct apk_name *name;
33 struct apk_package *oldpkg = change->old_pkg;
34 struct apk_package *newpkg = change->new_pkg;
35 const char *msg = NULL;
36 char status[32];
37 apk_blob_t *oneversion = NULL;
38 int r;
39
40 snprintf(status, sizeof(status), "(%i/%i)", cur+1, total);
41
42 name = newpkg ? newpkg->name : oldpkg->name;
43 if (oldpkg == NULL) {
44 msg = "Installing";
45 oneversion = newpkg->version;
46 } else if (newpkg == NULL) {
47 msg = "Purging";
48 oneversion = oldpkg->version;
49 } else if (newpkg == oldpkg) {
50 if (change->reinstall) {
51 if (pkg_available(db, newpkg))
52 msg = "Reinstalling";
53 else
54 msg = "[APK unavailable, skipped] Reinstalling";
55 } else if (change->old_repository_tag != change->new_repository_tag) {
56 msg = "Updating pinning";
57 }
58 oneversion = newpkg->version;
59 } else {
60 r = apk_pkg_version_compare(newpkg, oldpkg);
61 switch (r) {
62 case APK_VERSION_LESS:
63 msg = "Downgrading";
64 break;
65 case APK_VERSION_EQUAL:
66 msg = "Replacing";
67 break;
68 case APK_VERSION_GREATER:
69 msg = "Upgrading";
70 break;
71 }
72 }
73 if (msg == NULL)
74 return FALSE;
75
76 if (oneversion) {
77 apk_message("%s %s %s" BLOB_FMT " (" BLOB_FMT ")",
78 status, msg,
79 name->name,
80 BLOB_PRINTF(db->repo_tags[change->new_repository_tag].tag),
81 BLOB_PRINTF(*oneversion));
82 } else {
83 apk_message("%s %s %s" BLOB_FMT " (" BLOB_FMT " -> " BLOB_FMT ")",
84 status, msg,
85 name->name,
86 BLOB_PRINTF(db->repo_tags[change->new_repository_tag].tag),
87 BLOB_PRINTF(*oldpkg->version),
88 BLOB_PRINTF(*newpkg->version));
89 }
90 return TRUE;
91}
92
93struct apk_stats {
94 unsigned int changes;
95 size_t bytes;
96 unsigned int packages;
97};
98
99static void count_change(struct apk_change *change, struct apk_stats *stats)
100{
101 if (change->new_pkg != change->old_pkg || change->reinstall) {
102 if (change->new_pkg != NULL) {
103 stats->bytes += change->new_pkg->installed_size;
104 stats->packages++;
105 }
106 if (change->old_pkg != NULL)
107 stats->packages++;
108 stats->changes++;
109 } else if (change->new_repository_tag != change->old_repository_tag) {
110 stats->packages++;
111 stats->changes++;
112 }
113}
114
115struct progress {
116 struct apk_stats done;
117 struct apk_stats total;
118 struct apk_package *pkg;
119};
120
121static void progress_cb(void *ctx, size_t installed_bytes)
122{
123 struct progress *prog = (struct progress *) ctx;
124 apk_print_progress(prog->done.bytes + prog->done.packages + installed_bytes,
125 prog->total.bytes + prog->total.packages);
126}
127
128static int dump_packages(struct apk_changeset *changeset,
129 int (*cmp)(struct apk_change *change),
130 const char *msg)
131{
132 struct apk_change *change;
133 struct apk_name *name;
134 struct apk_indent indent = { .indent = 2 };
135 int match = 0;
136
137 foreach_array_item(change, changeset->changes) {
138 if (!cmp(change))
139 continue;
140 if (match == 0)
141 printf("%s:\n", msg);
142 if (change->new_pkg != NULL)
143 name = change->new_pkg->name;
144 else
145 name = change->old_pkg->name;
146
147 apk_print_indented(&indent, APK_BLOB_STR(name->name));
148 match++;
149 }
150 if (match)
151 printf("\n");
152 return match;
153}
154
155static int cmp_remove(struct apk_change *change)
156{
157 return change->new_pkg == NULL;
158}
159
160static int cmp_new(struct apk_change *change)
161{
162 return change->old_pkg == NULL;
163}
164
165static int cmp_reinstall(struct apk_change *change)
166{
167 return change->reinstall;
168}
169
170static int cmp_downgrade(struct apk_change *change)
171{
172 if (change->new_pkg == NULL || change->old_pkg == NULL)
173 return 0;
174 if (apk_pkg_version_compare(change->new_pkg, change->old_pkg)
175 & APK_VERSION_LESS)
176 return 1;
177 return 0;
178}
179
180static int cmp_upgrade(struct apk_change *change)
181{
182 if (change->new_pkg == NULL || change->old_pkg == NULL)
183 return 0;
184
185 /* Count swapping package as upgrade too - this can happen if
186 * same package version is used after it was rebuilt against
187 * newer libraries. Basically, different (and probably newer)
188 * package, but equal version number. */
189 if ((apk_pkg_version_compare(change->new_pkg, change->old_pkg) &
190 (APK_VERSION_GREATER | APK_VERSION_EQUAL)) &&
191 (change->new_pkg != change->old_pkg))
192 return 1;
193
194 return 0;
195}
196
197static void run_triggers(struct apk_database *db, struct apk_changeset *changeset)
198{
199 struct apk_change *change;
200 struct apk_installed_package *ipkg;
201
202 if (apk_db_fire_triggers(db) == 0)
203 return;
204
205 foreach_array_item(change, changeset->changes) {
206 struct apk_package *pkg = change->new_pkg;
207 if (pkg == NULL)
208 continue;
209 ipkg = pkg->ipkg;
210 if (ipkg == NULL || ipkg->pending_triggers->num == 0)
211 continue;
212
213 *apk_string_array_add(&ipkg->pending_triggers) = NULL;
214 apk_ipkg_run_script(ipkg, db, APK_SCRIPT_TRIGGER,
215 ipkg->pending_triggers->item);
216 apk_string_array_free(&ipkg->pending_triggers);
217 }
218}
219
220#define PRE_COMMIT_HOOK 0
221#define POST_COMMIT_HOOK 1
222
223struct apk_commit_hook {
224 struct apk_database *db;
225 int type;
226};
227
228static int run_commit_hook(void *ctx, int dirfd, const char *file)
229{
230 static char *const commit_hook_str[] = { "pre-commit", "post-commit" };
231 struct apk_commit_hook *hook = (struct apk_commit_hook *) ctx;
232 struct apk_database *db = hook->db;
233 char fn[PATH_MAX], *argv[] = { fn, (char *) commit_hook_str[hook->type], NULL };
234
235 if ((apk_flags & (APK_NO_SCRIPTS | APK_SIMULATE)) != 0)
236 return 0;
237
238 snprintf(fn, sizeof(fn), "etc/apk/commit_hooks.d" "/%s", file);
239 if (apk_verbosity >= 2) apk_message("Executing: %s %s", fn, commit_hook_str[hook->type]);
240
241 if (apk_db_run_script(db, fn, argv) < 0 && hook->type == PRE_COMMIT_HOOK)
242 return -2;
243
244 return 0;
245}
246
247static int run_commit_hooks(struct apk_database *db, int type)
248{
249 struct apk_commit_hook hook = { .db = db, .type = type };
250 return apk_dir_foreach_file(openat(db->root_fd, "etc/apk/commit_hooks.d", O_RDONLY | O_CLOEXEC),
251 run_commit_hook, &hook);
252}
253
254int apk_solver_commit_changeset(struct apk_database *db,
255 struct apk_changeset *changeset,
256 struct apk_dependency_array *world)
257{
258 struct progress prog;
259 struct apk_change *change;
260 char buf[32], size_unit;
261 ssize_t size_diff = 0;
262 int r, errors = 0;
263
264 if (apk_db_check_world(db, world) != 0) {
265 apk_error("Not committing changes due to missing repository tags. Use --force to override.");
266 return -1;
267 }
268
269 if (changeset->changes == NULL)
270 goto all_done;
271
272 /* Count what needs to be done */
273 memset(&prog, 0, sizeof(prog));
274 foreach_array_item(change, changeset->changes) {
275 count_change(change, &prog.total);
276 if (change->new_pkg)
277 size_diff += change->new_pkg->installed_size / 1024;
278 if (change->old_pkg)
279 size_diff -= change->old_pkg->installed_size / 1024;
280 }
281 size_unit = 'K';
282 if (labs(size_diff) > 10000) {
283 size_diff /= 1024;
284 size_unit = 'M';
285 }
286
287 if ((apk_verbosity > 1 || (apk_flags & APK_INTERACTIVE)) &&
288 !(apk_flags & APK_SIMULATE)) {
289 r = dump_packages(changeset, cmp_remove,
290 "The following packages will be REMOVED");
291 r += dump_packages(changeset, cmp_downgrade,
292 "The following packages will be DOWNGRADED");
293 if (r || (apk_flags & APK_INTERACTIVE) || apk_verbosity > 2) {
294 r += dump_packages(changeset, cmp_new,
295 "The following NEW packages will be installed");
296 r += dump_packages(changeset, cmp_upgrade,
297 "The following packages will be upgraded");
298 r += dump_packages(changeset, cmp_reinstall,
299 "The following packages will be reinstalled");
300 printf("After this operation, %zd %ciB of %s.\n",
301 (size_diff < 0) ? -size_diff : size_diff,
302 size_unit,
303 (size_diff < 0) ?
304 "disk space will be freed" :
305 "additional disk space will be used");
306 }
307 if (r > 0 && (apk_flags & APK_INTERACTIVE)) {
308 printf("Do you want to continue [Y/n]? ");
309 fflush(stdout);
310 r = fgetc(stdin);
311 if (r != 'y' && r != 'Y' && r != '\n')
312 return -1;
313 }
314 }
315
316 if (run_commit_hooks(db, PRE_COMMIT_HOOK) == -2)
317 return -1;
318
319 /* Go through changes */
320 foreach_array_item(change, changeset->changes) {
321 r = change->old_pkg &&
322 (change->old_pkg->ipkg->broken_files ||
323 change->old_pkg->ipkg->broken_script);
324 if (print_change(db, change, prog.done.changes, prog.total.changes)) {
325 prog.pkg = change->new_pkg;
326 progress_cb(&prog, 0);
327
328 if (!(apk_flags & APK_SIMULATE) &&
329 ((change->old_pkg != change->new_pkg) ||
330 (change->reinstall && pkg_available(db, change->new_pkg)))) {
331 r = apk_db_install_pkg(db, change->old_pkg, change->new_pkg,
332 progress_cb, &prog) != 0;
333 }
334 if (r == 0 && change->new_pkg && change->new_pkg->ipkg)
335 change->new_pkg->ipkg->repository_tag = change->new_repository_tag;
336 }
337 errors += r;
338 count_change(change, &prog.done);
339 }
340 apk_print_progress(prog.total.bytes + prog.total.packages,
341 prog.total.bytes + prog.total.packages);
342
343 apk_db_update_directory_permissions(db);
344 run_triggers(db, changeset);
345
346all_done:
347 apk_dependency_array_copy(&db->world, world);
348 apk_db_write_config(db);
349 run_commit_hooks(db, POST_COMMIT_HOOK);
350
351 if (!db->performing_self_upgrade) {
352 if (errors)
353 snprintf(buf, sizeof(buf), "%d error%s;", errors,
354 errors > 1 ? "s" : "");
355 else
356 strcpy(buf, "OK:");
357 if (apk_verbosity > 1) {
358 apk_message("%s %d packages, %d dirs, %d files, %zu MiB",
359 buf,
360 db->installed.stats.packages,
361 db->installed.stats.dirs,
362 db->installed.stats.files,
363 db->installed.stats.bytes / (1024 * 1024));
364 } else {
365 apk_message("%s %zu MiB in %d packages",
366 buf,
367 db->installed.stats.bytes / (1024 * 1024),
368 db->installed.stats.packages);
369 }
370 }
371 return errors;
372}
373
374enum {
375 STATE_PRESENT = 0x80000000,
376 STATE_MISSING = 0x40000000,
377 STATE_COUNT_MASK = 0x0000ffff,
378};
379
380struct print_state {
381 struct apk_database *db;
382 struct apk_dependency_array *world;
383 struct apk_indent i;
384 struct apk_name_array *missing;
385 const char *label;
386 int num_labels;
387 int match;
388};
389
390static void label_start(struct print_state *ps, const char *text)
391{
392 if (ps->label) {
393 printf(" %s:\n", ps->label);
394 ps->label = NULL;
395 ps->i.x = ps->i.indent = 0;
396 ps->num_labels++;
397 }
398 if (ps->i.x == 0) {
399 ps->i.x = printf(" %s", text);
400 ps->i.indent = ps->i.x + 1;
401 }
402}
403static void label_end(struct print_state *ps)
404{
405 if (ps->i.x != 0) {
406 printf("\n");
407 ps->i.x = ps->i.indent = 0;
408 }
409}
410
411static void print_pinning_errors(struct print_state *ps, struct apk_package *pkg, unsigned int tag)
412{
413 struct apk_database *db = ps->db;
414 int i;
415
416 if (pkg->ipkg != NULL)
417 return;
418
419 if (!(pkg->repos & db->available_repos)) {
420 label_start(ps, "masked in:");
421 apk_print_indented_fmt(&ps->i, "--no-network");
422 } else if (pkg->repos == BIT(APK_REPOSITORY_CACHED)) {
423 label_start(ps, "masked in:");
424 apk_print_indented_fmt(&ps->i, "cache");
425 } else {
426 if (pkg->repos & apk_db_get_pinning_mask_repos(db, APK_DEFAULT_PINNING_MASK | BIT(tag)))
427 return;
428 for (i = 0; i < db->num_repo_tags; i++) {
429 if (pkg->repos & db->repo_tags[i].allowed_repos) {
430 label_start(ps, "masked in:");
431 apk_print_indented(&ps->i, db->repo_tags[i].tag);
432 }
433 }
434 }
435 label_end(ps);
436}
437
438static void print_conflicts(struct print_state *ps, struct apk_package *pkg)
439{
440 struct apk_provider *p;
441 struct apk_dependency *d;
442 char tmp[256];
443 int once;
444
445 foreach_array_item(p, pkg->name->providers) {
446 if (p->pkg == pkg || !p->pkg->marked)
447 continue;
448 label_start(ps, "conflicts:");
449 apk_print_indented_fmt(&ps->i, PKG_VER_FMT, PKG_VER_PRINTF(p->pkg));
450 }
451 foreach_array_item(d, pkg->provides) {
452 once = 1;
453 foreach_array_item(p, d->name->providers) {
454 if (!p->pkg->marked)
455 continue;
456 if (d->version == &apk_null_blob &&
457 p->version == &apk_null_blob)
458 continue;
459 if (once && p->pkg == pkg) {
460 once = 0;
461 continue;
462 }
463 label_start(ps, "conflicts:");
464 apk_print_indented_fmt(
465 &ps->i, PKG_VER_FMT "[%s]",
466 PKG_VER_PRINTF(p->pkg),
467 apk_dep_snprintf(tmp, sizeof(tmp), d));
468 }
469 }
470 label_end(ps);
471}
472
473static void print_dep(struct apk_package *pkg0, struct apk_dependency *d0, struct apk_package *pkg, void *ctx)
474{
475 struct print_state *ps = (struct print_state *) ctx;
476 const char *label = (ps->match & APK_DEP_SATISFIES) ? "satisfies:" : "breaks:";
477 char tmp[256];
478
479 label_start(ps, label);
480 if (pkg0 == NULL)
481 apk_print_indented_fmt(&ps->i, "world[%s]", apk_dep_snprintf(tmp, sizeof(tmp), d0));
482 else
483 apk_print_indented_fmt(&ps->i, PKG_VER_FMT "[%s]",
484 PKG_VER_PRINTF(pkg0),
485 apk_dep_snprintf(tmp, sizeof(tmp), d0));
486}
487
488static void print_deps(struct print_state *ps, struct apk_package *pkg, int match)
489{
490 ps->match = match;
491 match |= APK_FOREACH_MARKED | APK_FOREACH_DEP;
492 apk_pkg_foreach_matching_dependency(NULL, ps->world, match|apk_foreach_genid(), pkg, print_dep, ps);
493 apk_pkg_foreach_reverse_dependency(pkg, match|apk_foreach_genid(), print_dep, ps);
494 label_end(ps);
495}
496
497static void analyze_package(struct print_state *ps, struct apk_package *pkg, unsigned int tag)
498{
499 char pkgtext[256];
500
501 snprintf(pkgtext, sizeof(pkgtext), PKG_VER_FMT, PKG_VER_PRINTF(pkg));
502 ps->label = pkgtext;
503
504 print_pinning_errors(ps, pkg, tag);
505 print_conflicts(ps, pkg);
506 print_deps(ps, pkg, APK_DEP_CONFLICTS);
507 if (ps->label == NULL)
508 print_deps(ps, pkg, APK_DEP_SATISFIES);
509}
510
511static void analyze_name(struct print_state *ps, struct apk_name *name)
512{
513 struct apk_name **pname0, *name0;
514 struct apk_provider *p0;
515 struct apk_dependency *d0;
516 char tmp[256];
517 int refs;
518
519 if (name->providers->num) {
520 snprintf(tmp, sizeof(tmp), "%s (virtual)", name->name);
521 ps->label = tmp;
522
523 label_start(ps, "provided by:");
524 foreach_array_item(p0, name->providers)
525 p0->pkg->name->state_int++;
526 foreach_array_item(p0, name->providers) {
527 name0 = p0->pkg->name;
528 refs = (name0->state_int & STATE_COUNT_MASK);
529 if (refs == name0->providers->num) {
530 /* name only */
531 apk_print_indented(&ps->i, APK_BLOB_STR(name0->name));
532 name0->state_int &= ~STATE_COUNT_MASK;
533 } else if (refs > 0) {
534 /* individual package */
535 apk_print_indented_fmt(&ps->i, PKG_VER_FMT, PKG_VER_PRINTF(p0->pkg));
536 name0->state_int--;
537 }
538 }
539 label_end(ps);
540 } else {
541 snprintf(tmp, sizeof(tmp), "%s (missing)", name->name);
542 ps->label = tmp;
543 }
544
545 label_start(ps, "required by:");
546 foreach_array_item(d0, ps->world) {
547 if (d0->name != name || d0->conflict)
548 continue;
549 apk_print_indented_fmt(&ps->i, "world[%s]",
550 apk_dep_snprintf(tmp, sizeof(tmp), d0));
551 }
552 foreach_array_item(pname0, name->rdepends) {
553 name0 = *pname0;
554 foreach_array_item(p0, name0->providers) {
555 if (!p0->pkg->marked)
556 continue;
557 foreach_array_item(d0, p0->pkg->depends) {
558 if (d0->name != name || d0->conflict)
559 continue;
560 apk_print_indented_fmt(&ps->i,
561 PKG_VER_FMT "[%s]",
562 PKG_VER_PRINTF(p0->pkg),
563 apk_dep_snprintf(tmp, sizeof(tmp), d0));
564 break;
565 }
566 if (d0 != NULL)
567 break;
568 }
569 }
570 label_end(ps);
571}
572
573static void analyze_deps(struct print_state *ps, struct apk_dependency_array *deps)
574{
575 struct apk_dependency *d0;
576 struct apk_name *name0;
577
578 foreach_array_item(d0, deps) {
579 name0 = d0->name;
580 if (d0->conflict)
581 continue;
582 if ((name0->state_int & (STATE_PRESENT | STATE_MISSING)) != 0)
583 continue;
584 name0->state_int |= STATE_MISSING;
585 analyze_name(ps, name0);
586 }
587}
588
589void apk_solver_print_errors(struct apk_database *db,
590 struct apk_changeset *changeset,
591 struct apk_dependency_array *world)
592{
593 struct print_state ps;
594 struct apk_change *change;
595 struct apk_dependency *p;
596
597 /* ERROR: unsatisfiable dependencies:
598 * name:
599 * required by: a b c d e
600 * not available in any repository
601 * name (virtual):
602 * required by: a b c d e
603 * provided by: foo bar zed
604 * pkg-1.2:
605 * masked by: @testing
606 * satisfies: a[pkg]
607 * conflicts: pkg-2.0 foo-1.2 bar-1.2
608 * breaks: b[pkg>2] c[foo>2] d[!pkg]
609 *
610 * When two packages provide same name 'foo':
611 * a-1:
612 * satisfies: world[a]
613 * conflicts: b-1[foo]
614 * b-1:
615 * satisfies: world[b]
616 * conflicts: a-1[foo]
617 *
618 * c-1:
619 * satisfies: world[a]
620 * conflicts: c-1[foo] (self-conflict by providing foo twice)
621 *
622 * When two packages get pulled in:
623 * a-1:
624 * satisfies: app1[so:a.so.1]
625 * conflicts: a-2
626 * a-2:
627 * satisfies: app2[so:a.so.2]
628 * conflicts: a-1
629 *
630 * satisfies lists all dependencies that is not satisfiable by
631 * any other selected version. or all of them with -v.
632 */
633
634 apk_error("unsatisfiable constraints:");
635
636 /* Construct information about names */
637 foreach_array_item(change, changeset->changes) {
638 struct apk_package *pkg = change->new_pkg;
639 if (pkg == NULL)
640 continue;
641 pkg->marked = 1;
642 pkg->name->state_int |= STATE_PRESENT;
643 foreach_array_item(p, pkg->provides)
644 p->name->state_int |= STATE_PRESENT;
645 }
646
647 /* Analyze is package, and missing names referred to */
648 ps = (struct print_state) {
649 .db = db,
650 .world = world,
651 };
652 analyze_deps(&ps, world);
653 foreach_array_item(change, changeset->changes) {
654 struct apk_package *pkg = change->new_pkg;
655 if (pkg == NULL)
656 continue;
657 analyze_package(&ps, pkg, change->new_repository_tag);
658 analyze_deps(&ps, pkg->depends);
659 }
660
661 if (ps.num_labels == 0)
662 printf(" Huh? Error reporter did not find the broken constraints.\n");
663}
664
665int apk_solver_commit(struct apk_database *db,
666 unsigned short solver_flags,
667 struct apk_dependency_array *world)
668{
669 struct apk_changeset changeset = {};
670 int r;
671
672 if (apk_db_check_world(db, world) != 0) {
673 apk_error("Not committing changes due to missing repository tags. Use --force to override.");
674 return -1;
675 }
676
677 r = apk_solver_solve(db, solver_flags, world, &changeset);
678 if (r == 0)
679 r = apk_solver_commit_changeset(db, &changeset, world);
680 else
681 apk_solver_print_errors(db, &changeset, world);
682
683 apk_change_array_free(&changeset.changes);
684 return r;
685}