XBPS Library API 20240111
The X Binary Package System
transaction_files.c
1/*-
2 * Copyright (c) 2019 Juan Romero Pardines.
3 * Copyright (c) 2019 Duncan Overbruck <mail@duncano.de>.
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <dirent.h>
28#include <errno.h>
29#include <fcntl.h>
30#include <limits.h>
31#include <stdlib.h>
32#include <string.h>
33
34#include <archive.h>
35#include <archive_entry.h>
36
37#include "xbps_api_impl.h"
38#include "uthash.h"
39
40enum type {
41 TYPE_LINK = 1,
42 TYPE_DIR,
43 TYPE_FILE,
44 TYPE_CONFFILE,
45};
46
47struct item {
48 char *file;
49 size_t len;
50 struct {
51 const char *pkgname;
52 const char *pkgver;
53 char *sha256;
54 const char *target;
55 uint64_t size;
56 enum type type;
57 /* index is the index of the package update/install/removal in the transaction
58 * and is used to decide which package should remove the given file or dir */
59 unsigned int index;
60 bool preserve;
61 bool update;
62 bool removepkg;
63 } old, new;
64 bool deleted;
66};
67
68/* hash table to look up files by path */
69static struct item *hashtab = NULL;
70
71/* list of files to be sorted using qsort */
72static struct item **items = NULL;
73static size_t itemsidx = 0;
74static size_t itemssz = 0;
75
76static struct item *
77lookupItem(const char *file)
78{
79 struct item *item = NULL;
80
81 assert(file);
82
83 HASH_FIND_STR(hashtab, file, item);
84 return item;
85}
86
87static struct item *
88addItem(const char *file)
89{
90 struct item *item = calloc(1, sizeof (struct item));
91 if (item == NULL)
92 return NULL;
93
94 assert(file);
95 assert(item);
96
97 if (itemsidx+1 >= itemssz) {
98 itemssz = itemssz ? itemssz*2 : 64;
99 items = realloc(items, itemssz*sizeof (struct item *));
100 if (items == NULL) {
101 free(item);
102 return NULL;
103 }
104 }
105 items[itemsidx++] = item;
106
107 if ((item->file = xbps_xasprintf(".%s", file)) == NULL) {
108 free(item);
109 return NULL;
110 }
111 item->len = strlen(item->file);
112
113 /*
114 * File paths are stored relative, but looked up absolute.
115 * Skip the leading . (dot) and substract it from the length.
116 */
117 HASH_ADD_KEYPTR(hh, hashtab, item->file+1, item->len-1, item);
118
119 return item;
120}
121
122static const char *
123typestr(enum type typ)
124{
125 switch (typ) {
126 case TYPE_LINK: return "symlink";
127 case TYPE_DIR: return "directory";
128 case TYPE_FILE: return "file";
129 case TYPE_CONFFILE: return "configuration file";
130 default: return NULL;
131 }
132}
133
134static bool
135match_preserved_file(struct xbps_handle *xhp, const char *file)
136{
137 if (xhp->preserved_files == NULL)
138 return false;
139
140 assert(file && *file == '.');
141 return xbps_match_string_in_array(xhp->preserved_files, file+1);
142}
143
144static bool
145can_delete_directory(const char *file, size_t len, size_t max)
146{
147 struct item *item;
148 size_t rmcount = 0, fcount = 0;
149 DIR *dp;
150
151 dp = opendir(file);
152 if (dp == NULL) {
153 if (errno == ENOENT) {
154 return true;
155 } else {
156 xbps_dbg_printf("[files] %s: %s: %s\n",
157 __func__, file, strerror(errno));
158 return false;
159 }
160 }
161
162 /*
163 * 1. Check if there is tracked directory content,
164 * which can't be deleted.
165 * 2. Count deletable directory content.
166 */
167 for (size_t i = 0; i < max; i++) {
168 item = items[i];
169 if (strncmp(item->file, file, len) == 0) {
170 if (!item->deleted) {
171 closedir(dp);
172 return false;
173 }
174 rmcount++;
175 }
176 }
177
178 /*
179 * Check if directory contains more files than we can
180 * delete.
181 */
182 while (readdir(dp) != 0)
183 fcount++;
184
185 /* ignore '.' and '..' */
186 fcount -= 2;
187
188 if (fcount <= rmcount) {
189 xbps_dbg_printf("[files] only removed %zu out of %zu files: %s\n",
190 rmcount, fcount, file);
191 }
192 closedir(dp);
193
194 return fcount <= rmcount;
195}
196
197static int
198collect_obsoletes(struct xbps_handle *xhp)
199{
200 /* These are symlinks in Void and must not be removed */
201 const char *basesymlinks[] = {
202 "/bin",
203 "/sbin",
204 "/usr/sbin",
205 "/lib",
206 "/lib32",
207 "/lib64",
208 "/usr/lib32",
209 "/usr/lib64",
210 "/var/run",
211 };
212 xbps_dictionary_t obsd;
213 struct item *item;
214 int rv = 0;
215
216 if (xhp->transd == NULL)
217 return ENOTSUP;
218
219 if (!xbps_dictionary_get_dict(xhp->transd, "obsolete_files", &obsd))
220 return ENOENT;
221
222 /*
223 * Iterate over all files, longest paths first,
224 * to check if directory contents of removed
225 * directories can be deleted.
226 *
227 * - Check if a file is obsolete
228 * - Check if obsolete file can be deleted.
229 * - Check if directory needs and can be deleted.
230 */
231 for (size_t i = 0; i < itemsidx; i++) {
232 xbps_array_t a;
233 const char *pkgname;
234 bool alloc = false, found = false;
235
236 item = items[i];
237
238 if (match_preserved_file(xhp, item->file)) {
239 xbps_dbg_printf("[obsoletes] %s: file exists on disk"
240 " and must be preserved: %s\n", item->old.pkgver, item->file);
241 continue;
242 }
243
244 if (item->new.type == 0) {
245 /*
246 * File was removed and is not provided by any
247 * new package.
248 * Probably obsolete.
249 */
250 if (item->old.preserve && item->old.update) {
251 xbps_dbg_printf("[files] %s: skipping `preserve` %s: %s\n",
252 item->old.pkgver, typestr(item->old.type), item->file);
253 continue;
254 }
255 } else if (item->new.type == TYPE_CONFFILE) {
256 /*
257 * Ignore conf files.
258 */
259 continue;
260 } else if (item->old.type == 0) {
261 /* XXX: add this new behaviour? */
262#if 0
263 /*
264 * Check if new file (untracked until now) exists.
265 */
266 if (access(item->file, F_OK) == 0) {
267 xbps_set_cb_state(xhp, XBPS_STATE_FILES_FAIL,
268 EEXIST, item->new.pkgver,
269 "%s: file `%s' already exists.",
270 item->new.pkgver, item->file);
271 rv = EEXIST;
272 break;
273 }
274#endif
275 continue;
276 } else if (item->old.type == TYPE_DIR &&
277 item->new.type != TYPE_DIR && item->new.type != 0) {
278 /*
279 * Directory replaced by a file or symlink.
280 * We MUST be able to delete the directory.
281 */
282 xbps_dbg_printf("[files] %s: directory changed to %s: %s\n",
283 item->new.pkgver, typestr(item->new.type), item->file);
284 if (!can_delete_directory(item->file, item->len, i)) {
285 xbps_set_cb_state(xhp, XBPS_STATE_FILES_FAIL,
286 ENOTEMPTY, item->old.pkgver,
287 "%s: directory `%s' can not be deleted.",
288 item->old.pkgver, item->file);
289 return ENOTEMPTY;
290 }
291 } else if (item->new.type != item->old.type) {
292 /*
293 * File type changed, we have to delete it.
294 */
295 } else {
296 continue;
297 }
298
299 /*
300 * Make sure to not remove any symlink of root directory.
301 */
302 for (uint8_t x = 0; x < __arraycount(basesymlinks); x++) {
303 if (strcmp(item->file+1, basesymlinks[x]) == 0) {
304 found = true;
305 break;
306 }
307 }
308 if (found)
309 continue;
310
311 /*
312 * Skip unexisting files and keep files with hash mismatch.
313 */
314 if (item->old.sha256 != NULL) {
315 rv = xbps_file_sha256_check(item->file, item->old.sha256);
316 switch (rv) {
317 case 0:
318 /* hash matches, we can safely delete and/or overwrite it */
319 break;
320 case ENOENT:
321 /* mark unexisting files as deleted and ignore ENOENT */
322 rv = 0;
323 item->deleted = true;
324 continue;
325 case ERANGE:
326 /* hash mismatch don't delete it */
327 rv = 0;
328 /*
329 * If the file is removed by uninstalling the package,
330 * no new package provides it and its not force removed,
331 * keep the file.
332 */
333 if (item->old.removepkg && !item->new.pkgname &&
334 (xhp->flags & XBPS_FLAG_FORCE_REMOVE_FILES) != 0) {
335 xbps_dbg_printf("[obsoletes] %s: SHA256 mismatch,"
336 " force remove %s: %s\n",
337 item->old.pkgname, typestr(item->old.type),
338 item->file+1);
339 break;
340 }
341 xbps_dbg_printf("[obsoletes] %s: SHA256 mismatch,"
342 " skipping remove %s: %s\n",
343 item->old.pkgname, typestr(item->old.type),
344 item->file+1);
345 continue;
346 default:
347 break;
348 }
349 }
350
351 /*
352 * On package removal without force, keep symlinks if target changed.
353 */
354 if (item->old.pkgname && item->old.removepkg &&
355 item->old.type == TYPE_LINK && !item->new.pkgname &&
356 (xhp->flags & XBPS_FLAG_FORCE_REMOVE_FILES) == 0) {
357 char path[PATH_MAX], *lnk;
358 const char *file = item->file+1;
359 if (strcmp(xhp->rootdir, "/") != 0) {
360 snprintf(path, sizeof(path), "%s%s",
361 xhp->rootdir, item->file+1);
362 file = path;
363 }
364 lnk = xbps_symlink_target(xhp, file, item->old.target);
365 if (lnk == NULL) {
366 xbps_dbg_printf("[obsoletes] %s "
367 "symlink_target: %s\n", item->file+1, strerror(errno));
368 continue;
369 }
370 if (strcmp(lnk, item->old.target) != 0) {
371 xbps_dbg_printf("[obsoletes] %s: skipping modified"
372 " symlink (stored `%s' current `%s'): %s\n",
373 item->old.pkgname, item->old.target, lnk, item->file+1);
374 free(lnk);
375 continue;
376 }
377 free(lnk);
378 }
379
380 /*
381 * Choose which package removes the obsolete files,
382 * based which packages is installed/unpacked first.
383 * This is necessary to not delete files
384 * after it was installed by another package.
385 */
386 if (item->old.pkgname && item->new.pkgname) {
387 pkgname = item->old.index > item->new.index ?
388 item->new.pkgname : item->old.pkgname;
389 } else if (item->old.pkgname) {
390 pkgname = item->old.pkgname;
391 } else {
392 pkgname = item->new.pkgname;
393 }
394 assert(pkgname);
395
396 xbps_dbg_printf("[obsoletes] %s: removes %s: %s\n",
397 pkgname, typestr(item->old.type), item->file+1);
398
399 /*
400 * Mark file as being deleted, this is used when
401 * checking if a directory can be deleted.
402 */
403 item->deleted = true;
404
405 /*
406 * Add file to the packages `obsolete_files` dict
407 */
408 if ((a = xbps_dictionary_get(obsd, pkgname)) == NULL) {
409 if (!(a = xbps_array_create()) ||
410 !(xbps_dictionary_set(obsd, pkgname, a)))
411 return ENOMEM;
412 alloc = true;
413 }
414 if (!xbps_array_add_cstring(a, item->file)) {
415 if (alloc)
416 xbps_object_release(a);
417 return ENOMEM;
418 }
419 if (alloc)
420 xbps_object_release(a);
421 }
422
423 return rv;
424}
425
426static int
427collect_file(struct xbps_handle *xhp, const char *file, size_t size,
428 const char *pkgname, const char *pkgver, unsigned int idx,
429 const char *sha256, enum type type, bool update, bool removepkg,
430 bool preserve, bool removefile, const char *target)
431{
432 struct item *item;
433
434 assert(file);
435
436 if ((item = lookupItem(file)) == NULL) {
437 item = addItem(file);
438 if (item == NULL)
439 return ENOMEM;
440 item->deleted = false;
441 goto add;
442 }
443
444 if (removefile) {
445 if (item->old.type == 0) {
446 /*
447 * File wasn't removed before.
448 */
449 } else if (type == TYPE_DIR && item->old.type == TYPE_DIR) {
450 /*
451 * Multiple packages removing the same directory.
452 * Record the last package to remove this directory.
453 */
454 if (idx < item->old.index || item->old.preserve)
455 return 0;
456 item->old.pkgname = pkgname;
457 item->old.pkgver = pkgver;
458 item->old.index = idx;
459 item->old.preserve = preserve;
460 item->old.update = update;
461 item->old.removepkg = removepkg;
462 return 0;
463 } else {
464 /*
465 * Multiple packages removing the same file.
466 * Shouldn't happen, but its not fatal.
467 */
468 xbps_dbg_printf("[files] %s: file already removed"
469 " by package `%s': %s\n", pkgver, item->old.pkgver, file);
470
471 /*
472 * Check if `preserve` is violated.
473 */
474 if (item->old.preserve && !preserve) {
475 xbps_set_cb_state(xhp, XBPS_STATE_FILES_FAIL,
476 EPERM, item->old.pkgver,
477 "%s: preserved file `%s' removed by %s.",
478 item->old.pkgver, file, pkgver);
479 return EPERM;
480 } else if (preserve && !item->old.preserve) {
481 xbps_set_cb_state(xhp, XBPS_STATE_FILES_FAIL,
482 EPERM, pkgver,
483 "%s: preserved file `%s' removed by %s.",
484 pkgver, file, item->old.pkgver);
485 return EPERM;
486 }
487 return 0;
488 }
489 goto add;
490 } else {
491 /*
492 * Multiple packages creating the same directory.
493 */
494 if (item->new.type == 0) {
495 /*
496 * File wasn't created before.
497 */
498 } else if (type == TYPE_DIR && item->new.type == TYPE_DIR) {
499 /*
500 * Multiple packages creating the same directory.
501 */
502 return 0;
503 } else {
504 /*
505 * Multiple packages creating the same file.
506 * This should never happen in a transaction.
507 */
508 xbps_set_cb_state(xhp, XBPS_STATE_FILES_FAIL,
509 EEXIST, pkgver,
510 "%s: file `%s' already installed by package %s.",
511 pkgver, file, item->new.pkgver);
512 if (xhp->flags & XBPS_FLAG_IGNORE_FILE_CONFLICTS)
513 return 0;
514
515 return EEXIST;
516 }
517 goto add;
518 }
519
520 return 0;
521add:
522 if (removefile) {
523 item->old.pkgname = pkgname;
524 item->old.pkgver = pkgver;
525 item->old.type = type;
526 item->old.size = size;
527 item->old.index = idx;
528 item->old.preserve = preserve;
529 item->old.update = update;
530 item->old.removepkg = removepkg;
531 item->old.target = target;
532 if (sha256)
533 item->old.sha256 = strdup(sha256);
534 } else {
535 item->new.pkgname = pkgname;
536 item->new.pkgver = pkgver;
537 item->new.type = type;
538 item->new.size = size;
539 item->new.index = idx;
540 item->new.preserve = preserve;
541 item->new.update = update;
542 item->new.removepkg = removepkg;
543 item->new.target = target;
544 }
545 if (item->old.type && item->new.type) {
546 /*
547 * The file was removed by one package
548 * and installed by another package.
549 */
550 if (strcmp(item->new.pkgname, item->old.pkgname) != 0) {
551 if (removefile) {
552 xbps_dbg_printf("[files] %s: %s moved to"
553 " package `%s': %s\n", pkgver, typestr(item->old.type),
554 item->new.pkgver, file);
555 } else {
556 xbps_dbg_printf("[files] %s: %s moved from"
557 " package `%s': %s\n", pkgver, typestr(item->new.type),
558 item->old.pkgver, file);
559 }
560 }
561 }
562
563 return 0;
564}
565
566static int
567collect_files(struct xbps_handle *xhp, xbps_dictionary_t d,
568 const char *pkgname, const char *pkgver, unsigned int idx,
569 bool update, bool removepkg, bool preserve, bool removefile)
570{
571 xbps_array_t a;
572 xbps_dictionary_t filed;
573 uint64_t size;
574 unsigned int i;
575 int rv = 0;
576 const char *file, *sha256 = NULL;
577 bool error = false;
578
579 if ((a = xbps_dictionary_get(d, "files"))) {
580 for (i = 0; i < xbps_array_count(a); i++) {
581 filed = xbps_array_get(a, i);
582 xbps_dictionary_get_cstring_nocopy(filed, "file", &file);
583 if (removefile)
584 xbps_dictionary_get_cstring_nocopy(filed, "sha256", &sha256);
585 size = 0;
586 xbps_dictionary_get_uint64(filed, "size", &size);
587 rv = collect_file(xhp, file, size, pkgname, pkgver, idx, sha256,
588 TYPE_FILE, update, removepkg, preserve, removefile, NULL);
589 if (rv == EEXIST) {
590 error = true;
591 continue;
592 } else if (rv != 0) {
593 goto out;
594 }
595 }
596 }
597 if ((a = xbps_dictionary_get(d, "conf_files"))) {
598 for (i = 0; i < xbps_array_count(a); i++) {
599 filed = xbps_array_get(a, i);
600 xbps_dictionary_get_cstring_nocopy(filed, "file", &file);
601 size = 0;
602 xbps_dictionary_get_uint64(filed, "size", &size);
603 if (removefile)
604 xbps_dictionary_get_cstring_nocopy(filed, "sha256", &sha256);
605#if 0
606 /* XXX: how to handle conf_file size */
607 if (removefile && stat(file, &st) != -1 && size != (uint64_t)st.st_size)
608 size = 0;
609#endif
610 rv = collect_file(xhp, file, size, pkgname, pkgver, idx, sha256,
611 TYPE_CONFFILE, update, removepkg, preserve, removefile, NULL);
612 if (rv == EEXIST) {
613 error = true;
614 continue;
615 } else if (rv != 0) {
616 goto out;
617 }
618 }
619 }
620 if ((a = xbps_dictionary_get(d, "links"))) {
621 for (i = 0; i < xbps_array_count(a); i++) {
622 const char *target = NULL;
623 filed = xbps_array_get(a, i);
624 xbps_dictionary_get_cstring_nocopy(filed, "file", &file);
625 xbps_dictionary_get_cstring_nocopy(filed, "target", &target);
626 assert(target);
627 rv = collect_file(xhp, file, 0, pkgname, pkgver, idx, NULL,
628 TYPE_LINK, update, removepkg, preserve, removefile, target);
629 if (rv == EEXIST) {
630 error = true;
631 continue;
632 } else if (rv != 0) {
633 goto out;
634 }
635 }
636 }
637 if ((a = xbps_dictionary_get(d, "dirs"))) {
638 for (i = 0; i < xbps_array_count(a); i++) {
639 filed = xbps_array_get(a, i);
640 xbps_dictionary_get_cstring_nocopy(filed, "file", &file);
641 rv = collect_file(xhp, file, 0, pkgname, pkgver, idx, NULL,
642 TYPE_DIR, update, removepkg, preserve, removefile, NULL);
643 if (rv == EEXIST) {
644 error = true;
645 continue;
646 } else if (rv != 0) {
647 goto out;
648 }
649 }
650 }
651
652out:
653 if (error)
654 rv = EEXIST;
655
656 return rv;
657}
658
659static int
660collect_binpkg_files(struct xbps_handle *xhp, xbps_dictionary_t pkg_repod,
661 unsigned int idx, bool update)
662{
663 xbps_dictionary_t filesd;
664 struct archive *ar = NULL;
665 struct archive_entry *entry;
666 struct stat st;
667 const char *pkgver, *pkgname;
668 char *bpkg;
669 /* size_t entry_size; */
670 int rv = 0, pkg_fd = -1;
671
672 xbps_dictionary_get_cstring_nocopy(pkg_repod, "pkgver", &pkgver);
673 assert(pkgver);
674 xbps_dictionary_get_cstring_nocopy(pkg_repod, "pkgname", &pkgname);
675 assert(pkgname);
676
677 bpkg = xbps_repository_pkg_path(xhp, pkg_repod);
678 if (bpkg == NULL) {
679 rv = errno;
680 goto out;
681 }
682
683 if ((ar = archive_read_new()) == NULL) {
684 rv = errno;
685 goto out;
686 }
687
688 /*
689 * Enable support for tar format and gzip/bzip2/lzma compression methods.
690 */
691 archive_read_support_filter_gzip(ar);
692 archive_read_support_filter_bzip2(ar);
693 archive_read_support_filter_xz(ar);
694 archive_read_support_filter_lz4(ar);
695 archive_read_support_filter_zstd(ar);
696 archive_read_support_format_tar(ar);
697
698 pkg_fd = open(bpkg, O_RDONLY|O_CLOEXEC);
699 if (pkg_fd == -1) {
700 rv = errno;
701 xbps_set_cb_state(xhp, XBPS_STATE_FILES_FAIL,
702 rv, pkgver,
703 "%s: failed to open binary package `%s': %s",
704 pkgver, bpkg, strerror(rv));
705 goto out;
706 }
707 if (fstat(pkg_fd, &st) == -1) {
708 rv = errno;
709 xbps_set_cb_state(xhp, XBPS_STATE_FILES_FAIL,
710 rv, pkgver,
711 "%s: failed to fstat binary package `%s': %s",
712 pkgver, bpkg, strerror(rv));
713 goto out;
714 }
715 if (archive_read_open_fd(ar, pkg_fd, st.st_blksize) == ARCHIVE_FATAL) {
716 rv = archive_errno(ar);
717 xbps_set_cb_state(xhp, XBPS_STATE_FILES_FAIL,
718 rv, pkgver,
719 "%s: failed to read binary package `%s': %s",
720 pkgver, bpkg, strerror(rv));
721 goto out;
722 }
723
724 for (uint8_t i = 0; i < 4; i++) {
725 const char *entry_pname;
726 int ar_rv = archive_read_next_header(ar, &entry);
727 if (ar_rv == ARCHIVE_EOF || ar_rv == ARCHIVE_FATAL)
728 break;
729 else if (ar_rv == ARCHIVE_RETRY)
730 continue;
731
732 entry_pname = archive_entry_pathname(entry);
733 if ((strcmp("./files.plist", entry_pname)) == 0) {
734 filesd = xbps_archive_get_dictionary(ar, entry);
735 if (filesd == NULL) {
736 rv = EINVAL;
737 goto out;
738 }
739 rv = collect_files(xhp, filesd, pkgname, pkgver, idx,
740 update, false, false, false);
741 xbps_object_release(filesd);
742 goto out;
743 }
744 archive_read_data_skip(ar);
745 }
746
747out:
748 if (pkg_fd != -1)
749 close(pkg_fd);
750 if (ar != NULL)
751 archive_read_free(ar);
752 free(bpkg);
753 return rv;
754}
755
756static int
757pathcmp(const void *l1, const void *l2)
758{
759 const struct item *a = *(const struct item * const*)l1;
760 const struct item *b = *(const struct item * const*)l2;
761 return (a->len < b->len) - (b->len < a->len);
762}
763
764static void
765cleanup(void)
766{
767 struct item *item, *itmp;
768
769 HASH_ITER(hh, hashtab, item, itmp) {
770 HASH_DEL(hashtab, item);
771 free(item->file);
772 free(item->old.sha256);
773 free(item->new.sha256);
774 free(item);
775 }
776 free(items);
777}
778
779/*
780 * xbps_transaction_files:
781 *
782 * - read files from each installed package in the transaction
783 * - read files from each binary package in the transaction
784 *
785 * - Find file conflicts between packages before starting the transaction
786 *
787 * - Schedule the removal of files
788 * - unlink files before extracting the package if the file type changed,
789 * a symlink becomes a directory or a directory becomes a regular file
790 * or symlink.
791 * - directories replaced with other file types are checked to be empty
792 * to avoid ENOTEMPTY while unpacking packages.
793 * - the last package removing a file out of a directory
794 * will try to remove that directory to avoid ENOTEMPTY
795 * - the removal of obsolete files and directory is sorted by
796 * path length so that directory content is removed before
797 * removing the directory.
798 */
799int HIDDEN
800xbps_transaction_files(struct xbps_handle *xhp, xbps_object_iterator_t iter)
801{
802 xbps_dictionary_t pkgd, filesd;
803 xbps_object_t obj;
804 xbps_trans_type_t ttype;
805 const char *pkgver, *pkgname;
806 int rv = 0;
807 unsigned int idx = 0;
808
809 assert(xhp);
810 assert(iter);
811
812 while ((obj = xbps_object_iterator_next(iter)) != NULL) {
813 bool update = false;
814
815 /* increment the index of the given package package in the transaction */
816 idx++;
817
818 /* ignore pkgs in hold mode or in unpacked state */
819 ttype = xbps_transaction_pkg_type(obj);
820 if (ttype == XBPS_TRANS_HOLD || ttype == XBPS_TRANS_CONFIGURE) {
821 continue;
822 }
823
824 if (!xbps_dictionary_get_cstring_nocopy(obj, "pkgver", &pkgver)) {
825 return EINVAL;
826 }
827 if (!xbps_dictionary_get_cstring_nocopy(obj, "pkgname", &pkgname)) {
828 return EINVAL;
829 }
830
831 update = (ttype == XBPS_TRANS_UPDATE);
832
833 if (ttype == XBPS_TRANS_INSTALL || ttype == XBPS_TRANS_REINSTALL || ttype == XBPS_TRANS_UPDATE) {
834 xbps_set_cb_state(xhp, XBPS_STATE_FILES, 0, pkgver,
835 "%s: collecting files...", pkgver);
836 rv = collect_binpkg_files(xhp, obj, idx, update);
837 if (rv != 0)
838 goto out;
839 }
840
841 /*
842 * Always just try to get the package from the pkgdb:
843 * update and remove always have a previous package,
844 * `hold` and `configure` are skipped.
845 * And finally the reason to do is, `install` could be
846 * a reinstallation, in which case the files list could
847 * different between old and new "install".
848 */
849 pkgd = xbps_pkgdb_get_pkg(xhp, pkgname);
850 if (pkgd) {
851 const char *oldpkgver;
852 bool preserve = false;
853 bool removepkg = (ttype == XBPS_TRANS_REMOVE);
854
855 xbps_dictionary_get_cstring_nocopy(pkgd, "pkgver", &oldpkgver);
856 if (!xbps_dictionary_get_bool(obj, "preserve", &preserve))
857 preserve = false;
858
859 filesd = xbps_pkgdb_get_pkg_files(xhp, pkgname);
860 if (filesd == NULL) {
861 continue;
862 }
863
864 assert(oldpkgver);
865 xbps_set_cb_state(xhp, XBPS_STATE_FILES, 0, oldpkgver,
866 "%s: collecting files...", oldpkgver);
867 rv = collect_files(xhp, filesd, pkgname, pkgver, idx,
868 update, removepkg, preserve, true);
869 if (rv != 0)
870 goto out;
871 }
872 }
873 xbps_object_iterator_reset(iter);
874
875 /*
876 * Sort items by path length, to make it easier to find files in
877 * directories.
878 */
879 qsort(items, itemsidx, sizeof (struct item *), pathcmp);
880
881 if (chdir(xhp->rootdir) == -1) {
882 rv = errno;
883 xbps_set_cb_state(xhp, XBPS_STATE_FILES_FAIL, rv, xhp->rootdir,
884 "failed to chdir to rootdir `%s': %s",
885 xhp->rootdir, strerror(errno));
886 }
887
888out:
889 if (rv != 0)
890 return rv;
891
892 rv = collect_obsoletes(xhp);
893 cleanup();
894 return rv;
895}
char rootdir[XBPS_MAXPATH]
Definition xbps.h:650
int flags
Definition xbps.h:679
xbps_dictionary_t transd
Definition xbps.h:583
Generic XBPS structure handler for initialization.
Definition xbps.h:550
xbps_dictionary_t xbps_pkgdb_get_pkg_files(struct xbps_handle *xhp, const char *pkg)
Definition pkgdb.c:482
xbps_dictionary_t xbps_pkgdb_get_pkg(struct xbps_handle *xhp, const char *pkg)
Definition pkgdb.c:379
bool xbps_match_string_in_array(xbps_array_t array, const char *val)
xbps_trans_type_t xbps_transaction_pkg_type(xbps_dictionary_t pkg_repod)
xbps_trans_type_t
Definition xbps.h:1320
char * xbps_xasprintf(const char *fmt,...) __attribute__((format(printf
int xbps_file_sha256_check(const char *file, const char *sha256)
Definition util_hash.c:201
char * xbps_symlink_target(struct xbps_handle *xhp, const char *path, const char *target)
Definition util.c:638
char * xbps_repository_pkg_path(struct xbps_handle *xhp, xbps_dictionary_t pkgd)
Definition util.c:393