projects/aports·blob v2.10.0 · refs

692 lines · 18.2 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_flags & APK_NO_COMMIT_HOOKS) != 0) {
240 apk_message("Skipping: %s %s", fn, commit_hook_str[hook->type]);
241 return 0;
242 }
243 if (apk_verbosity >= 2) apk_message("Executing: %s %s", fn, commit_hook_str[hook->type]);
244
245 if (apk_db_run_script(db, fn, argv) < 0 && hook->type == PRE_COMMIT_HOOK)
246 return -2;
247
248 return 0;
249}
250
251static int run_commit_hooks(struct apk_database *db, int type)
252{
253 struct apk_commit_hook hook = { .db = db, .type = type };
254 return apk_dir_foreach_file(openat(db->root_fd, "etc/apk/commit_hooks.d", O_RDONLY | O_CLOEXEC),
255 run_commit_hook, &hook);
256}
257
258int apk_solver_commit_changeset(struct apk_database *db,
259 struct apk_changeset *changeset,
260 struct apk_dependency_array *world)
261{
262 struct progress prog;
263 struct apk_change *change;
264 char buf[32], size_unit;
265 ssize_t size_diff = 0;
266 int r, errors = 0;
267
268 if (apk_db_check_world(db, world) != 0) {
269 apk_error("Not committing changes due to missing repository tags. "
270 "Use --force-broken-world to override.");
271 return -1;
272 }
273
274 if (changeset->changes == NULL)
275 goto all_done;
276
277 /* Count what needs to be done */
278 memset(&prog, 0, sizeof(prog));
279 foreach_array_item(change, changeset->changes) {
280 count_change(change, &prog.total);
281 if (change->new_pkg)
282 size_diff += change->new_pkg->installed_size / 1024;
283 if (change->old_pkg)
284 size_diff -= change->old_pkg->installed_size / 1024;
285 }
286 size_unit = 'K';
287 if (labs(size_diff) > 10000) {
288 size_diff /= 1024;
289 size_unit = 'M';
290 }
291
292 if ((apk_verbosity > 1 || (apk_flags & APK_INTERACTIVE)) &&
293 !(apk_flags & APK_SIMULATE)) {
294 r = dump_packages(changeset, cmp_remove,
295 "The following packages will be REMOVED");
296 r += dump_packages(changeset, cmp_downgrade,
297 "The following packages will be DOWNGRADED");
298 if (r || (apk_flags & APK_INTERACTIVE) || apk_verbosity > 2) {
299 r += dump_packages(changeset, cmp_new,
300 "The following NEW packages will be installed");
301 r += dump_packages(changeset, cmp_upgrade,
302 "The following packages will be upgraded");
303 r += dump_packages(changeset, cmp_reinstall,
304 "The following packages will be reinstalled");
305 printf("After this operation, %zd %ciB of %s.\n",
306 (size_diff < 0) ? -size_diff : size_diff,
307 size_unit,
308 (size_diff < 0) ?
309 "disk space will be freed" :
310 "additional disk space will be used");
311 }
312 if (r > 0 && (apk_flags & APK_INTERACTIVE)) {
313 printf("Do you want to continue [Y/n]? ");
314 fflush(stdout);
315 r = fgetc(stdin);
316 if (r != 'y' && r != 'Y' && r != '\n')
317 return -1;
318 }
319 }
320
321 if (run_commit_hooks(db, PRE_COMMIT_HOOK) == -2)
322 return -1;
323
324 /* Go through changes */
325 foreach_array_item(change, changeset->changes) {
326 r = change->old_pkg &&
327 (change->old_pkg->ipkg->broken_files ||
328 change->old_pkg->ipkg->broken_script);
329 if (print_change(db, change, prog.done.changes, prog.total.changes)) {
330 prog.pkg = change->new_pkg;
331 progress_cb(&prog, 0);
332
333 if (!(apk_flags & APK_SIMULATE) &&
334 ((change->old_pkg != change->new_pkg) ||
335 (change->reinstall && pkg_available(db, change->new_pkg)))) {
336 r = apk_db_install_pkg(db, change->old_pkg, change->new_pkg,
337 progress_cb, &prog) != 0;
338 }
339 if (r == 0 && change->new_pkg && change->new_pkg->ipkg)
340 change->new_pkg->ipkg->repository_tag = change->new_repository_tag;
341 }
342 errors += r;
343 count_change(change, &prog.done);
344 }
345 apk_print_progress(prog.total.bytes + prog.total.packages,
346 prog.total.bytes + prog.total.packages);
347
348 apk_db_update_directory_permissions(db);
349 run_triggers(db, changeset);
350
351all_done:
352 apk_dependency_array_copy(&db->world, world);
353 apk_db_write_config(db);
354 run_commit_hooks(db, POST_COMMIT_HOOK);
355
356 if (!db->performing_self_upgrade) {
357 if (errors)
358 snprintf(buf, sizeof(buf), "%d error%s;", errors,
359 errors > 1 ? "s" : "");
360 else
361 strcpy(buf, "OK:");
362 if (apk_verbosity > 1) {
363 apk_message("%s %d packages, %d dirs, %d files, %zu MiB",
364 buf,
365 db->installed.stats.packages,
366 db->installed.stats.dirs,
367 db->installed.stats.files,
368 db->installed.stats.bytes / (1024 * 1024));
369 } else {
370 apk_message("%s %zu MiB in %d packages",
371 buf,
372 db->installed.stats.bytes / (1024 * 1024),
373 db->installed.stats.packages);
374 }
375 }
376 return errors;
377}
378
379enum {
380 STATE_PRESENT = 0x80000000,
381 STATE_MISSING = 0x40000000,
382 STATE_COUNT_MASK = 0x0000ffff,
383};
384
385struct print_state {
386 struct apk_database *db;
387 struct apk_dependency_array *world;
388 struct apk_indent i;
389 struct apk_name_array *missing;
390 const char *label;
391 int num_labels;
392 int match;
393};
394
395static void label_start(struct print_state *ps, const char *text)
396{
397 if (ps->label) {
398 printf(" %s:\n", ps->label);
399 ps->label = NULL;
400 ps->i.x = ps->i.indent = 0;
401 ps->num_labels++;
402 }
403 if (ps->i.x == 0) {
404 ps->i.x = printf(" %s", text);
405 ps->i.indent = ps->i.x + 1;
406 }
407}
408static void label_end(struct print_state *ps)
409{
410 if (ps->i.x != 0) {
411 printf("\n");
412 ps->i.x = ps->i.indent = 0;
413 }
414}
415
416static void print_pinning_errors(struct print_state *ps, struct apk_package *pkg, unsigned int tag)
417{
418 struct apk_database *db = ps->db;
419 int i;
420
421 if (pkg->ipkg != NULL)
422 return;
423
424 if (!(pkg->repos & db->available_repos)) {
425 label_start(ps, "masked in:");
426 apk_print_indented_fmt(&ps->i, "--no-network");
427 } else if (pkg->repos == BIT(APK_REPOSITORY_CACHED)) {
428 label_start(ps, "masked in:");
429 apk_print_indented_fmt(&ps->i, "cache");
430 } else {
431 if (pkg->repos & apk_db_get_pinning_mask_repos(db, APK_DEFAULT_PINNING_MASK | BIT(tag)))
432 return;
433 for (i = 0; i < db->num_repo_tags; i++) {
434 if (pkg->repos & db->repo_tags[i].allowed_repos) {
435 label_start(ps, "masked in:");
436 apk_print_indented(&ps->i, db->repo_tags[i].tag);
437 }
438 }
439 }
440 label_end(ps);
441}
442
443static void print_conflicts(struct print_state *ps, struct apk_package *pkg)
444{
445 struct apk_provider *p;
446 struct apk_dependency *d;
447 char tmp[256];
448 int once;
449
450 foreach_array_item(p, pkg->name->providers) {
451 if (p->pkg == pkg || !p->pkg->marked)
452 continue;
453 label_start(ps, "conflicts:");
454 apk_print_indented_fmt(&ps->i, PKG_VER_FMT, PKG_VER_PRINTF(p->pkg));
455 }
456 foreach_array_item(d, pkg->provides) {
457 once = 1;
458 foreach_array_item(p, d->name->providers) {
459 if (!p->pkg->marked)
460 continue;
461 if (d->version == &apk_null_blob &&
462 p->version == &apk_null_blob)
463 continue;
464 if (once && p->pkg == pkg &&
465 p->version == d->version) {
466 once = 0;
467 continue;
468 }
469 label_start(ps, "conflicts:");
470 apk_print_indented_fmt(
471 &ps->i, PKG_VER_FMT "[%s]",
472 PKG_VER_PRINTF(p->pkg),
473 apk_dep_snprintf(tmp, sizeof(tmp), d));
474 }
475 }
476 label_end(ps);
477}
478
479static void print_dep(struct apk_package *pkg0, struct apk_dependency *d0, struct apk_package *pkg, void *ctx)
480{
481 struct print_state *ps = (struct print_state *) ctx;
482 const char *label = (ps->match & APK_DEP_SATISFIES) ? "satisfies:" : "breaks:";
483 char tmp[256];
484
485 label_start(ps, label);
486 if (pkg0 == NULL)
487 apk_print_indented_fmt(&ps->i, "world[%s]", apk_dep_snprintf(tmp, sizeof(tmp), d0));
488 else
489 apk_print_indented_fmt(&ps->i, PKG_VER_FMT "[%s]",
490 PKG_VER_PRINTF(pkg0),
491 apk_dep_snprintf(tmp, sizeof(tmp), d0));
492}
493
494static void print_deps(struct print_state *ps, struct apk_package *pkg, int match)
495{
496 ps->match = match;
497 match |= APK_FOREACH_MARKED | APK_FOREACH_DEP;
498 apk_pkg_foreach_matching_dependency(NULL, ps->world, match|apk_foreach_genid(), pkg, print_dep, ps);
499 apk_pkg_foreach_reverse_dependency(pkg, match|apk_foreach_genid(), print_dep, ps);
500 label_end(ps);
501}
502
503static void analyze_package(struct print_state *ps, struct apk_package *pkg, unsigned int tag)
504{
505 char pkgtext[256];
506
507 snprintf(pkgtext, sizeof(pkgtext), PKG_VER_FMT, PKG_VER_PRINTF(pkg));
508 ps->label = pkgtext;
509
510 print_pinning_errors(ps, pkg, tag);
511 print_conflicts(ps, pkg);
512 print_deps(ps, pkg, APK_DEP_CONFLICTS);
513 if (ps->label == NULL)
514 print_deps(ps, pkg, APK_DEP_SATISFIES);
515}
516
517static void analyze_name(struct print_state *ps, struct apk_name *name)
518{
519 struct apk_name **pname0, *name0;
520 struct apk_provider *p0;
521 struct apk_dependency *d0;
522 char tmp[256];
523 int refs;
524
525 if (name->providers->num) {
526 snprintf(tmp, sizeof(tmp), "%s (virtual)", name->name);
527 ps->label = tmp;
528
529 label_start(ps, "provided by:");
530 foreach_array_item(p0, name->providers)
531 p0->pkg->name->state_int++;
532 foreach_array_item(p0, name->providers) {
533 name0 = p0->pkg->name;
534 refs = (name0->state_int & STATE_COUNT_MASK);
535 if (refs == name0->providers->num) {
536 /* name only */
537 apk_print_indented(&ps->i, APK_BLOB_STR(name0->name));
538 name0->state_int &= ~STATE_COUNT_MASK;
539 } else if (refs > 0) {
540 /* individual package */
541 apk_print_indented_fmt(&ps->i, PKG_VER_FMT, PKG_VER_PRINTF(p0->pkg));
542 name0->state_int--;
543 }
544 }
545 label_end(ps);
546 } else {
547 snprintf(tmp, sizeof(tmp), "%s (missing)", name->name);
548 ps->label = tmp;
549 }
550
551 label_start(ps, "required by:");
552 foreach_array_item(d0, ps->world) {
553 if (d0->name != name || d0->conflict)
554 continue;
555 apk_print_indented_fmt(&ps->i, "world[%s]",
556 apk_dep_snprintf(tmp, sizeof(tmp), d0));
557 }
558 foreach_array_item(pname0, name->rdepends) {
559 name0 = *pname0;
560 foreach_array_item(p0, name0->providers) {
561 if (!p0->pkg->marked)
562 continue;
563 foreach_array_item(d0, p0->pkg->depends) {
564 if (d0->name != name || d0->conflict)
565 continue;
566 apk_print_indented_fmt(&ps->i,
567 PKG_VER_FMT "[%s]",
568 PKG_VER_PRINTF(p0->pkg),
569 apk_dep_snprintf(tmp, sizeof(tmp), d0));
570 break;
571 }
572 if (d0 != NULL)
573 break;
574 }
575 }
576 label_end(ps);
577}
578
579static void analyze_deps(struct print_state *ps, struct apk_dependency_array *deps)
580{
581 struct apk_dependency *d0;
582 struct apk_name *name0;
583
584 foreach_array_item(d0, deps) {
585 name0 = d0->name;
586 if (d0->conflict)
587 continue;
588 if ((name0->state_int & (STATE_PRESENT | STATE_MISSING)) != 0)
589 continue;
590 name0->state_int |= STATE_MISSING;
591 analyze_name(ps, name0);
592 }
593}
594
595void apk_solver_print_errors(struct apk_database *db,
596 struct apk_changeset *changeset,
597 struct apk_dependency_array *world)
598{
599 struct print_state ps;
600 struct apk_change *change;
601 struct apk_dependency *p;
602
603 /* ERROR: unsatisfiable dependencies:
604 * name:
605 * required by: a b c d e
606 * not available in any repository
607 * name (virtual):
608 * required by: a b c d e
609 * provided by: foo bar zed
610 * pkg-1.2:
611 * masked by: @testing
612 * satisfies: a[pkg]
613 * conflicts: pkg-2.0 foo-1.2 bar-1.2
614 * breaks: b[pkg>2] c[foo>2] d[!pkg]
615 *
616 * When two packages provide same name 'foo':
617 * a-1:
618 * satisfies: world[a]
619 * conflicts: b-1[foo]
620 * b-1:
621 * satisfies: world[b]
622 * conflicts: a-1[foo]
623 *
624 * c-1:
625 * satisfies: world[a]
626 * conflicts: c-1[foo] (self-conflict by providing foo twice)
627 *
628 * When two packages get pulled in:
629 * a-1:
630 * satisfies: app1[so:a.so.1]
631 * conflicts: a-2
632 * a-2:
633 * satisfies: app2[so:a.so.2]
634 * conflicts: a-1
635 *
636 * satisfies lists all dependencies that is not satisfiable by
637 * any other selected version. or all of them with -v.
638 */
639
640 apk_error("unsatisfiable constraints:");
641
642 /* Construct information about names */
643 foreach_array_item(change, changeset->changes) {
644 struct apk_package *pkg = change->new_pkg;
645 if (pkg == NULL)
646 continue;
647 pkg->marked = 1;
648 pkg->name->state_int |= STATE_PRESENT;
649 foreach_array_item(p, pkg->provides)
650 p->name->state_int |= STATE_PRESENT;
651 }
652
653 /* Analyze is package, and missing names referred to */
654 ps = (struct print_state) {
655 .db = db,
656 .world = world,
657 };
658 analyze_deps(&ps, world);
659 foreach_array_item(change, changeset->changes) {
660 struct apk_package *pkg = change->new_pkg;
661 if (pkg == NULL)
662 continue;
663 analyze_package(&ps, pkg, change->new_repository_tag);
664 analyze_deps(&ps, pkg->depends);
665 }
666
667 if (ps.num_labels == 0)
668 printf(" Huh? Error reporter did not find the broken constraints.\n");
669}
670
671int apk_solver_commit(struct apk_database *db,
672 unsigned short solver_flags,
673 struct apk_dependency_array *world)
674{
675 struct apk_changeset changeset = {};
676 int r;
677
678 if (apk_db_check_world(db, world) != 0) {
679 apk_error("Not committing changes due to missing repository tags. "
680 "Use --force-broken-world to override.");
681 return -1;
682 }
683
684 r = apk_solver_solve(db, solver_flags, world, &changeset);
685 if (r == 0)
686 r = apk_solver_commit_changeset(db, &changeset, world);
687 else
688 apk_solver_print_errors(db, &changeset, world);
689
690 apk_change_array_free(&changeset.changes);
691 return r;
692}