XBPS Library API 20260501
The X Binary Package System
package_unpack.c
1/*-
2 * Copyright (c) 2008-2015 Juan Romero Pardines.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include <sys/stat.h>
27
28#include <errno.h>
29#include <fcntl.h>
30#include <libgen.h>
31#include <limits.h>
32#include <stdbool.h>
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36#include <unistd.h>
37
38#include <archive.h>
39#include <archive_entry.h>
40
41#include "xbps_api_impl.h"
42
43#define EXTRACT_FLAGS ARCHIVE_EXTRACT_SECURE_NODOTDOT | \
44 ARCHIVE_EXTRACT_SECURE_SYMLINKS | \
45 ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS | \
46 ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_PERM | \
47 ARCHIVE_EXTRACT_UNLINK
48#define FEXTRACT_FLAGS ARCHIVE_EXTRACT_OWNER | EXTRACT_FLAGS
49
50
51static int
52set_extract_flags(uid_t euid)
53{
54 int flags;
55
56 if (euid == 0)
57 flags = FEXTRACT_FLAGS;
58 else
59 flags = EXTRACT_FLAGS;
60
61 return flags;
62}
63
64static bool
65match_preserved_file(struct xbps_handle *xhp, const char *entry)
66{
67 const char *file;
68
69 if (xhp->preserved_files == NULL)
70 return false;
71
72 if (entry[0] == '.' && entry[1] != '\0') {
73 file = entry + 1;
74 } else {
75 file = entry;
76 }
77
78 return xbps_match_string_in_array(xhp->preserved_files, file);
79}
80
81static int
82unpack_archive(struct xbps_handle *xhp,
83 xbps_dictionary_t pkg_repod,
84 const char *pkgver,
85 const char *fname,
86 struct archive *ar)
87{
88 xbps_dictionary_t binpkg_filesd, pkg_filesd, obsd;
89 xbps_array_t array, obsoletes;
90 const struct stat *entry_statp;
91 struct stat st;
92 struct xbps_unpack_cb_data xucd;
93 struct archive_entry *entry;
94 ssize_t entry_size;
95 const char *entry_pname, *pkgname;
96 const char *file;
97 int ar_rv, rv, error, entry_type, flags;
98 bool preserve, file_exists, keep_conf_file;
99 bool skip_extract, force, xucd_stats;
100 uid_t euid;
101
102 binpkg_filesd = pkg_filesd = NULL;
103 force = preserve = false;
104 xucd_stats = false;
105 ar_rv = rv = error = 0;
106
107 xbps_dictionary_get_bool(pkg_repod, "preserve", &preserve);
108
109 memset(&xucd, 0, sizeof(xucd));
110
111 euid = geteuid();
112
113 if (!xbps_dictionary_get_cstring_nocopy(pkg_repod, "pkgname", &pkgname)) {
114 return EINVAL;
115 }
116
117 if (xhp->flags & XBPS_FLAG_FORCE_UNPACK) {
118 force = true;
119 }
120
121 /*
122 * Remove obsolete files.
123 */
124 if (!preserve &&
125 xbps_dictionary_get_dict(xhp->transd, "obsolete_files", &obsd) &&
126 (obsoletes = xbps_dictionary_get(obsd, pkgname))) {
127 for (unsigned int i = 0; i < xbps_array_count(obsoletes); i++) {
128 const char *obsolete = NULL;
129 xbps_array_get_cstring_nocopy(obsoletes, i, &obsolete);
130 if (remove(obsolete) == -1) {
131 xbps_set_cb_state(xhp,
132 XBPS_STATE_REMOVE_FILE_OBSOLETE_FAIL,
133 errno, pkgver,
134 "%s: failed to remove obsolete entry `%s': %s",
135 pkgver, obsolete, strerror(errno));
136 continue;
137 }
138 xbps_set_cb_state(xhp,
139 XBPS_STATE_REMOVE_FILE_OBSOLETE,
140 0, pkgver, "%s: removed obsolete entry: %s", pkgver, obsolete);
141 }
142 }
143
144 /*
145 * Process the archive files.
146 */
147 flags = set_extract_flags(euid);
148
149 /*
150 * First get all metadata files on archive in this order:
151 * - INSTALL <optional>
152 * - REMOVE <optional>
153 * - props.plist <required> but currently ignored
154 * - files.plist <required>
155 *
156 * The XBPS package must contain props and files plists, otherwise
157 * it's not a valid package.
158 */
159 for (uint8_t i = 0; i < 4; i++) {
160 ar_rv = archive_read_next_header(ar, &entry);
161 if (ar_rv == ARCHIVE_EOF || ar_rv == ARCHIVE_FATAL)
162 break;
163
164 entry_pname = archive_entry_pathname(entry);
165 if (!entry_pname)
167
168 if (strcmp("./INSTALL", entry_pname) == 0 ||
169 strcmp("./REMOVE", entry_pname) == 0 ||
170 strcmp("./props.plist", entry_pname) == 0) {
171 archive_read_data_skip(ar);
172 } else if (strcmp("./files.plist", entry_pname) == 0) {
173 binpkg_filesd = xbps_archive_get_dictionary(ar, entry);
174 if (binpkg_filesd == NULL) {
175 rv = EINVAL;
176 goto out;
177 }
178 break;
179 } else {
180 break;
181 }
182 }
183 /*
184 * If there was any error extracting files from archive, error out.
185 */
186 if (ar_rv == ARCHIVE_FATAL) {
187 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL, rv, pkgver,
188 "%s: [unpack] 1: failed to extract files: %s",
189 pkgver, archive_error_string(ar));
190 rv = EINVAL;
191 goto out;
192 }
193 /*
194 * Bail out if required metadata files are not in archive.
195 */
196 if (binpkg_filesd == NULL) {
197 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL, ENODEV, pkgver,
198 "%s: [unpack] invalid binary package `%s'.", pkgver, fname);
199 rv = ENODEV;
200 goto out;
201 }
202
203 /*
204 * Internalize current pkg metadata files plist.
205 */
206 pkg_filesd = xbps_pkgdb_get_pkg_files(xhp, pkgname);
207
208 /*
209 * Unpack all files on archive now.
210 */
211 for (;;) {
212 ar_rv = archive_read_next_header(ar, &entry);
213 if (ar_rv == ARCHIVE_EOF || ar_rv == ARCHIVE_FATAL)
214 break;
215 else if (ar_rv == ARCHIVE_RETRY)
216 continue;
217
218 entry_pname = archive_entry_pathname(entry);
219 if (!entry_pname)
221
222 entry_size = archive_entry_size(entry);
223 entry_type = archive_entry_filetype(entry);
224 entry_statp = archive_entry_stat(entry);
225 /*
226 * Ignore directories from archive.
227 */
228 if (entry_type == AE_IFDIR) {
229 archive_read_data_skip(ar);
230 continue;
231 }
232
233 /*
234 * Archive entries have been required to start with ./
235 * instead of / for a long time, we can enforce it.
236 */
237 if (entry_pname[0] != '.') {
238 xbps_error_printf("%s: invalid archive entry: %s\n",
239 pkgver, entry_pname);
240 archive_read_data_skip(ar);
241 continue;
242 }
243
244 /*
245 * Prepare unpack callback ops.
246 */
247 if (xhp->unpack_cb != NULL) {
248 xucd.xhp = xhp;
249 xucd.pkgver = pkgver;
250 xucd.entry = entry_pname;
251 xucd.entry_size = entry_size;
252 xucd.entry_is_conf = false;
253 /*
254 * Compute total entries in progress data, if set.
255 * total_entries = files + conf_files + links.
256 */
257 if (binpkg_filesd && !xucd_stats) {
258 array = xbps_dictionary_get(binpkg_filesd, "files");
259 xucd.entry_total_count +=
260 (ssize_t)xbps_array_count(array);
261 array = xbps_dictionary_get(binpkg_filesd, "conf_files");
262 xucd.entry_total_count +=
263 (ssize_t)xbps_array_count(array);
264 array = xbps_dictionary_get(binpkg_filesd, "links");
265 xucd.entry_total_count +=
266 (ssize_t)xbps_array_count(array);
267 xucd_stats = true;
268 }
269 }
270 /*
271 * Skip files that match noextract patterns from configuration file.
272 */
273 if (xhp->noextract && xbps_patterns_match(xhp->noextract, entry_pname+1)) {
274 xbps_dbg_printf("[unpack] %s skipped (matched by a pattern)\n", entry_pname+1);
275 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FILE_PRESERVED, 0,
276 pkgver, "%s: file `%s' won't be extracted, "
277 "it matches a noextract pattern.", pkgver, entry_pname);
278 archive_read_data_skip(ar);
279 continue;
280 }
281 /*
282 * Always check that extracted file exists and hash
283 * doesn't match, in that case overwrite the file.
284 * Otherwise skip extracting it.
285 */
286 skip_extract = file_exists = keep_conf_file = false;
287 if (lstat(entry_pname, &st) == 0)
288 file_exists = true;
289 /*
290 * Check if the file to be extracted must be preserved, if true,
291 * pass to the next file.
292 */
293 if (file_exists && match_preserved_file(xhp, entry_pname)) {
294 archive_read_data_skip(ar);
295 xbps_dbg_printf("[unpack] `%s' exists on disk "
296 "and must be preserved, skipping.\n", entry_pname);
297 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FILE_PRESERVED, 0,
298 pkgver, "%s: file `%s' won't be extracted, "
299 "it's preserved.", pkgver, entry_pname);
300 continue;
301 }
302
303 /*
304 * Check if current entry is a configuration file,
305 * that should be kept.
306 */
307 if (!force && (entry_type == AE_IFREG)) {
308 file = entry_pname + 1;
309 keep_conf_file = xbps_entry_is_a_conf_file(binpkg_filesd, file);
310 }
311
312 /*
313 * If file to be extracted does not match the file type of
314 * file currently stored on disk and is not a conf file
315 * that should be kept, remove file on disk.
316 */
317 if (file_exists && !keep_conf_file &&
318 ((entry_statp->st_mode & S_IFMT) != (st.st_mode & S_IFMT)))
319 (void)remove(entry_pname);
320
321 if (!force && (entry_type == AE_IFREG)) {
322 if (file_exists && (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) {
323 /*
324 * Handle configuration files.
325 * Skip packages that don't have "conf_files"
326 * array on its XBPS_PKGPROPS
327 * dictionary.
328 */
329 if (keep_conf_file) {
330 if (xhp->unpack_cb != NULL)
331 xucd.entry_is_conf = true;
332
333 rv = xbps_entry_install_conf_file(xhp,
334 binpkg_filesd, pkg_filesd, entry,
335 entry_pname, pkgver, S_ISLNK(st.st_mode));
336 if (rv == -1) {
337 /* error */
338 goto out;
339 } else if (rv == 0) {
340 /*
341 * Keep curfile as is.
342 */
343 skip_extract = true;
344 }
345 rv = 0;
346 } else {
347 rv = xbps_file_hash_check_dictionary(
348 xhp, binpkg_filesd, "files", file);
349 if (rv == -1) {
350 /* error */
352 "%s: failed to check"
353 " hash for `%s': %s\n",
354 pkgver, entry_pname,
355 strerror(errno));
356 goto out;
357 } else if (rv == 0) {
358 /*
359 * hash match, skip extraction.
360 */
362 "%s: file %s "
363 "matches existing SHA256, "
364 "skipping...\n",
365 pkgver, entry_pname);
366 skip_extract = true;
367 }
368 rv = 0;
369 }
370 }
371 }
372 /*
373 * Check if current uid/gid differs from file in binpkg,
374 * and change permissions if true.
375 */
376 if ((!force && file_exists && skip_extract && (euid == 0)) &&
377 (((archive_entry_uid(entry) != st.st_uid)) ||
378 ((archive_entry_gid(entry) != st.st_gid)))) {
379 if (lchown(entry_pname,
380 archive_entry_uid(entry),
381 archive_entry_gid(entry)) != 0) {
383 "%s: failed "
384 "to set uid/gid to %"PRIu64":%"PRIu64" (%s)\n",
385 pkgver, archive_entry_uid(entry),
386 archive_entry_gid(entry),
387 strerror(errno));
388 } else {
389 xbps_dbg_printf("%s: entry %s changed "
390 "uid/gid to %"PRIu64":%"PRIu64".\n", pkgver, entry_pname,
391 archive_entry_uid(entry),
392 archive_entry_gid(entry));
393 }
394 }
395 /*
396 * Check if current file mode differs from file mode
397 * in binpkg and apply perms if true.
398 */
399 if (!force && file_exists && skip_extract &&
400 (archive_entry_mode(entry) != st.st_mode)) {
401 if (chmod(entry_pname,
402 archive_entry_mode(entry)) != 0) {
404 "%s: failed "
405 "to set perms %s to %s: %s\n",
406 pkgver, archive_entry_strmode(entry),
407 entry_pname,
408 strerror(errno));
409 rv = EINVAL;
410 goto out;
411 }
412 xbps_dbg_printf("%s: entry %s changed file "
413 "mode to %s.\n", pkgver, entry_pname,
414 archive_entry_strmode(entry));
415 }
416 if (!force && skip_extract) {
417 archive_read_data_skip(ar);
418 continue;
419 }
420 /*
421 * Reset entry_pname again because if entry's pathname
422 * has been changed it will become a dangling pointer.
423 */
424 entry_pname = archive_entry_pathname(entry);
425 if (!entry_pname)
427 /*
428 * Extract entry from archive.
429 */
430 if (archive_read_extract(ar, entry, flags) != 0) {
431 error = xbps_archive_errno(ar);
432 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL,
433 error, pkgver,
434 "%s: [unpack] failed to extract file `%s': %s",
435 pkgver, entry_pname, strerror(error));
436 break;
437 } else {
438 if (xhp->unpack_cb != NULL) {
439 xucd.entry = entry_pname;
440 xucd.entry_extract_count++;
441 (*xhp->unpack_cb)(&xucd, xhp->unpack_cb_data);
442 }
443 }
444 }
445 /*
446 * If there was any error extracting files from archive, error out.
447 */
448 if (error || ar_rv == ARCHIVE_FATAL) {
449 rv = error;
450 if (!rv)
451 rv = ar_rv;
452 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL, rv, pkgver,
453 "%s: [unpack] failed to extract files: %s",
454 pkgver, strerror(rv));
455 goto out;
456 }
457 /*
458 * Externalize binpkg files.plist to disk, if not empty.
459 */
460 if (xbps_dictionary_count(binpkg_filesd)) {
461 char *buf;
462 mode_t prev_umask;
463 prev_umask = umask(022);
464 buf = xbps_xasprintf("%s/.%s-files.plist", xhp->metadir, pkgname);
465 if (!xbps_dictionary_externalize_to_file(binpkg_filesd, buf)) {
466 rv = errno;
467 umask(prev_umask);
468 free(buf);
469 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL,
470 rv, pkgver, "%s: [unpack] failed to externalize pkg "
471 "pkg metadata files: %s", pkgver, strerror(rv));
472 goto out;
473 }
474 umask(prev_umask);
475 free(buf);
476 }
477out:
478 /*
479 * If unpacked pkg has no files, remove its files metadata plist.
480 */
481 if (!xbps_dictionary_count(binpkg_filesd)) {
482 char *buf = xbps_xasprintf("%s/.%s-files.plist", xhp->metadir, pkgname);
483 unlink(buf);
484 free(buf);
485 }
486 xbps_object_release(binpkg_filesd);
487
488 return rv;
489}
490
491int HIDDEN
492xbps_unpack_binary_pkg(struct xbps_handle *xhp, xbps_dictionary_t pkg_repod)
493{
494 char bpkg[PATH_MAX];
495 struct archive *ar = NULL;
496 struct stat st;
497 const char *pkgver;
498 ssize_t l;
499 int pkg_fd = -1, rv = 0;
500 mode_t myumask;
501
502 assert(xbps_object_type(pkg_repod) == XBPS_TYPE_DICTIONARY);
503
504 xbps_dictionary_get_cstring_nocopy(pkg_repod, "pkgver", &pkgver);
505 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK, 0, pkgver, NULL);
506
507 l = xbps_pkg_path(xhp, bpkg, sizeof(bpkg), pkg_repod);
508 if (l < 0) {
509 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL,
510 errno, pkgver,
511 "%s: [unpack] cannot determine binary package "
512 "file: %s", pkgver, strerror(errno));
513 return -l;
514 }
515
516 if ((ar = archive_read_new()) == NULL)
517 return ENOMEM;
518 /*
519 * Enable support for tar format and some compression methods.
520 */
521 archive_read_support_filter_gzip(ar);
522 archive_read_support_filter_bzip2(ar);
523 archive_read_support_filter_xz(ar);
524 archive_read_support_filter_lz4(ar);
525 archive_read_support_filter_zstd(ar);
526 archive_read_support_format_tar(ar);
527
528 myumask = umask(022);
529
530 pkg_fd = open(bpkg, O_RDONLY|O_CLOEXEC);
531 if (pkg_fd == -1) {
532 rv = errno;
533 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL,
534 rv, pkgver,
535 "%s: [unpack] failed to open binary package `%s': %s",
536 pkgver, bpkg, strerror(rv));
537 goto out;
538 }
539 if (fstat(pkg_fd, &st) == -1) {
540 rv = errno;
541 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL,
542 rv, pkgver,
543 "%s: [unpack] failed to fstat binary package `%s': %s",
544 pkgver, bpkg, strerror(rv));
545 goto out;
546 }
547 if (archive_read_open_fd(ar, pkg_fd, st.st_blksize) == ARCHIVE_FATAL) {
548 rv = xbps_archive_errno(ar);
549 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL,
550 rv, pkgver,
551 "%s: [unpack] failed to read binary package `%s': %s",
552 pkgver, bpkg, strerror(rv));
553 goto out;
554 }
555 /*
556 * Externalize pkg files dictionary to metadir.
557 */
558 if (access(xhp->metadir, R_OK|X_OK) == -1) {
559 rv = errno;
560 if (rv != ENOENT)
561 goto out;
562
563 if (xbps_mkpath(xhp->metadir, 0755) == -1) {
564 rv = errno;
565 goto out;
566 }
567 }
568 /*
569 * Extract archive files.
570 */
571 if ((rv = unpack_archive(xhp, pkg_repod, pkgver, bpkg, ar)) != 0) {
572 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL, rv, pkgver,
573 "%s: [unpack] failed to unpack files from archive: %s",
574 pkgver, strerror(rv));
575 goto out;
576 }
577 /*
578 * Set package state to unpacked.
579 */
580 if ((rv = xbps_set_pkg_state_dictionary(pkg_repod,
581 XBPS_PKG_STATE_UNPACKED)) != 0) {
582 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL,
583 rv, pkgver,
584 "%s: [unpack] failed to set state to unpacked: %s",
585 pkgver, strerror(rv));
586 }
587 /* register alternatives */
588 if ((rv = xbps_alternatives_register(xhp, pkg_repod)) != 0) {
589 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL,
590 rv, pkgver,
591 "%s: [unpack] failed to register alternatives: %s",
592 pkgver, strerror(rv));
593 }
594
595out:
596 if (pkg_fd != -1)
597 close(pkg_fd);
598 if (ar != NULL)
599 archive_read_free(ar);
600
601 /* restore */
602 umask(myumask);
603
604 return rv;
605}
int xbps_alternatives_register(struct xbps_handle *xhp, xbps_dictionary_t pkgd)
xbps_dictionary_t transd
Definition xbps.h:597
void * unpack_cb_data
Definition xbps.h:621
void(* unpack_cb)(const struct xbps_unpack_cb_data *, void *)
Definition xbps.h:614
int flags
Definition xbps.h:693
char metadir[XBPS_MAXPATH]
Definition xbps.h:678
Structure to be passed to the unpack function callback.
Definition xbps.h:507
Generic XBPS structure handler for initialization.
Definition xbps.h:560
#define xbps_unreachable()
Log and abort for code that should be unreachable.
Definition xbps.h:783
void xbps_error_printf(const char *fmt,...)
Prints error messages to stderr.
Definition log.c:93
void xbps_dbg_printf(const char *fmt,...)
Prints debug messages to stderr.
Definition log.c:67
xbps_dictionary_t xbps_pkgdb_get_pkg_files(struct xbps_handle *xhp, const char *pkg)
Definition pkgdb.c:514
int xbps_set_pkg_state_dictionary(xbps_dictionary_t dict, pkg_state_t state)
bool xbps_match_string_in_array(xbps_array_t array, const char *val)
char * xbps_xasprintf(const char *fmt,...) __attribute__((format(printf
bool xbps_patterns_match(xbps_array_t patterns, const char *path)
Definition util.c:729
int xbps_mkpath(const char *path, mode_t mode)
Definition mkpath.c:42
ssize_t xbps_pkg_path(struct xbps_handle *xhp, char *dst, size_t dstsz, xbps_dictionary_t pkgd)
Definition util.c:321