XBPS Library API 20260225
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 = strchr(entry, '.') + 1;
74 assert(file);
75 } else {
76 file = entry;
77 }
78
79 return xbps_match_string_in_array(xhp->preserved_files, file);
80}
81
82static int
83unpack_archive(struct xbps_handle *xhp,
84 xbps_dictionary_t pkg_repod,
85 const char *pkgver,
86 const char *fname,
87 struct archive *ar)
88{
89 xbps_dictionary_t binpkg_filesd, pkg_filesd, obsd;
90 xbps_array_t array, obsoletes;
91 const struct stat *entry_statp;
92 struct stat st;
93 struct xbps_unpack_cb_data xucd;
94 struct archive_entry *entry;
95 ssize_t entry_size;
96 const char *entry_pname, *pkgname;
97 const char *file;
98 int ar_rv, rv, error, entry_type, flags;
99 bool preserve, file_exists, keep_conf_file;
100 bool skip_extract, force, xucd_stats;
101 uid_t euid;
102
103 binpkg_filesd = pkg_filesd = NULL;
104 force = preserve = false;
105 xucd_stats = false;
106 ar_rv = rv = error = 0;
107
108 xbps_dictionary_get_bool(pkg_repod, "preserve", &preserve);
109
110 memset(&xucd, 0, sizeof(xucd));
111
112 euid = geteuid();
113
114 if (!xbps_dictionary_get_cstring_nocopy(pkg_repod, "pkgname", &pkgname)) {
115 return EINVAL;
116 }
117
118 if (xhp->flags & XBPS_FLAG_FORCE_UNPACK) {
119 force = true;
120 }
121
122 /*
123 * Remove obsolete files.
124 */
125 if (!preserve &&
126 xbps_dictionary_get_dict(xhp->transd, "obsolete_files", &obsd) &&
127 (obsoletes = xbps_dictionary_get(obsd, pkgname))) {
128 for (unsigned int i = 0; i < xbps_array_count(obsoletes); i++) {
129 const char *obsolete = NULL;
130 xbps_array_get_cstring_nocopy(obsoletes, i, &obsolete);
131 if (remove(obsolete) == -1) {
132 xbps_set_cb_state(xhp,
133 XBPS_STATE_REMOVE_FILE_OBSOLETE_FAIL,
134 errno, pkgver,
135 "%s: failed to remove obsolete entry `%s': %s",
136 pkgver, obsolete, strerror(errno));
137 continue;
138 }
139 xbps_set_cb_state(xhp,
140 XBPS_STATE_REMOVE_FILE_OBSOLETE,
141 0, pkgver, "%s: removed obsolete entry: %s", pkgver, obsolete);
142 }
143 }
144
145 /*
146 * Process the archive files.
147 */
148 flags = set_extract_flags(euid);
149
150 /*
151 * First get all metadata files on archive in this order:
152 * - INSTALL <optional>
153 * - REMOVE <optional>
154 * - props.plist <required> but currently ignored
155 * - files.plist <required>
156 *
157 * The XBPS package must contain props and files plists, otherwise
158 * it's not a valid package.
159 */
160 for (uint8_t i = 0; i < 4; i++) {
161 ar_rv = archive_read_next_header(ar, &entry);
162 if (ar_rv == ARCHIVE_EOF || ar_rv == ARCHIVE_FATAL)
163 break;
164
165 entry_pname = archive_entry_pathname(entry);
166
167 if (strcmp("./INSTALL", entry_pname) == 0 ||
168 strcmp("./REMOVE", entry_pname) == 0 ||
169 strcmp("./props.plist", entry_pname) == 0) {
170 archive_read_data_skip(ar);
171 } else if (strcmp("./files.plist", entry_pname) == 0) {
172 binpkg_filesd = xbps_archive_get_dictionary(ar, entry);
173 if (binpkg_filesd == NULL) {
174 rv = EINVAL;
175 goto out;
176 }
177 break;
178 } else {
179 break;
180 }
181 }
182 /*
183 * If there was any error extracting files from archive, error out.
184 */
185 if (ar_rv == ARCHIVE_FATAL) {
186 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL, rv, pkgver,
187 "%s: [unpack] 1: failed to extract files: %s",
188 pkgver, archive_error_string(ar));
189 rv = EINVAL;
190 goto out;
191 }
192 /*
193 * Bail out if required metadata files are not in archive.
194 */
195 if (binpkg_filesd == NULL) {
196 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL, ENODEV, pkgver,
197 "%s: [unpack] invalid binary package `%s'.", pkgver, fname);
198 rv = ENODEV;
199 goto out;
200 }
201
202 /*
203 * Internalize current pkg metadata files plist.
204 */
205 pkg_filesd = xbps_pkgdb_get_pkg_files(xhp, pkgname);
206
207 /*
208 * Unpack all files on archive now.
209 */
210 for (;;) {
211 ar_rv = archive_read_next_header(ar, &entry);
212 if (ar_rv == ARCHIVE_EOF || ar_rv == ARCHIVE_FATAL)
213 break;
214 else if (ar_rv == ARCHIVE_RETRY)
215 continue;
216
217 entry_pname = archive_entry_pathname(entry);
218 entry_size = archive_entry_size(entry);
219 entry_type = archive_entry_filetype(entry);
220 entry_statp = archive_entry_stat(entry);
221 /*
222 * Ignore directories from archive.
223 */
224 if (entry_type == AE_IFDIR) {
225 archive_read_data_skip(ar);
226 continue;
227 }
228 /*
229 * Prepare unpack callback ops.
230 */
231 if (xhp->unpack_cb != NULL) {
232 xucd.xhp = xhp;
233 xucd.pkgver = pkgver;
234 xucd.entry = entry_pname;
235 xucd.entry_size = entry_size;
236 xucd.entry_is_conf = false;
237 /*
238 * Compute total entries in progress data, if set.
239 * total_entries = files + conf_files + links.
240 */
241 if (binpkg_filesd && !xucd_stats) {
242 array = xbps_dictionary_get(binpkg_filesd, "files");
243 xucd.entry_total_count +=
244 (ssize_t)xbps_array_count(array);
245 array = xbps_dictionary_get(binpkg_filesd, "conf_files");
246 xucd.entry_total_count +=
247 (ssize_t)xbps_array_count(array);
248 array = xbps_dictionary_get(binpkg_filesd, "links");
249 xucd.entry_total_count +=
250 (ssize_t)xbps_array_count(array);
251 xucd_stats = true;
252 }
253 }
254 /*
255 * Skip files that match noextract patterns from configuration file.
256 */
257 if (xhp->noextract && xbps_patterns_match(xhp->noextract, entry_pname+1)) {
258 xbps_dbg_printf("[unpack] %s skipped (matched by a pattern)\n", entry_pname+1);
259 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FILE_PRESERVED, 0,
260 pkgver, "%s: file `%s' won't be extracted, "
261 "it matches a noextract pattern.", pkgver, entry_pname);
262 archive_read_data_skip(ar);
263 continue;
264 }
265 /*
266 * Always check that extracted file exists and hash
267 * doesn't match, in that case overwrite the file.
268 * Otherwise skip extracting it.
269 */
270 skip_extract = file_exists = keep_conf_file = false;
271 if (lstat(entry_pname, &st) == 0)
272 file_exists = true;
273 /*
274 * Check if the file to be extracted must be preserved, if true,
275 * pass to the next file.
276 */
277 if (file_exists && match_preserved_file(xhp, entry_pname)) {
278 archive_read_data_skip(ar);
279 xbps_dbg_printf("[unpack] `%s' exists on disk "
280 "and must be preserved, skipping.\n", entry_pname);
281 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FILE_PRESERVED, 0,
282 pkgver, "%s: file `%s' won't be extracted, "
283 "it's preserved.", pkgver, entry_pname);
284 continue;
285 }
286
287 /*
288 * Check if current entry is a configuration file,
289 * that should be kept.
290 */
291 if (!force && (entry_type == AE_IFREG)) {
292 file = strchr(entry_pname, '.') + 1;
293 assert(file != NULL);
294 keep_conf_file = xbps_entry_is_a_conf_file(binpkg_filesd, file);
295 }
296
297 /*
298 * If file to be extracted does not match the file type of
299 * file currently stored on disk and is not a conf file
300 * that should be kept, remove file on disk.
301 */
302 if (file_exists && !keep_conf_file &&
303 ((entry_statp->st_mode & S_IFMT) != (st.st_mode & S_IFMT)))
304 (void)remove(entry_pname);
305
306 if (!force && (entry_type == AE_IFREG)) {
307 if (file_exists && (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) {
308 /*
309 * Handle configuration files.
310 * Skip packages that don't have "conf_files"
311 * array on its XBPS_PKGPROPS
312 * dictionary.
313 */
314 if (keep_conf_file) {
315 if (xhp->unpack_cb != NULL)
316 xucd.entry_is_conf = true;
317
318 rv = xbps_entry_install_conf_file(xhp,
319 binpkg_filesd, pkg_filesd, entry,
320 entry_pname, pkgver, S_ISLNK(st.st_mode));
321 if (rv == -1) {
322 /* error */
323 goto out;
324 } else if (rv == 0) {
325 /*
326 * Keep curfile as is.
327 */
328 skip_extract = true;
329 }
330 rv = 0;
331 } else {
332 rv = xbps_file_hash_check_dictionary(
333 xhp, binpkg_filesd, "files", file);
334 if (rv == -1) {
335 /* error */
337 "%s: failed to check"
338 " hash for `%s': %s\n",
339 pkgver, entry_pname,
340 strerror(errno));
341 goto out;
342 } else if (rv == 0) {
343 /*
344 * hash match, skip extraction.
345 */
347 "%s: file %s "
348 "matches existing SHA256, "
349 "skipping...\n",
350 pkgver, entry_pname);
351 skip_extract = true;
352 }
353 rv = 0;
354 }
355 }
356 }
357 /*
358 * Check if current uid/gid differs from file in binpkg,
359 * and change permissions if true.
360 */
361 if ((!force && file_exists && skip_extract && (euid == 0)) &&
362 (((archive_entry_uid(entry) != st.st_uid)) ||
363 ((archive_entry_gid(entry) != st.st_gid)))) {
364 if (lchown(entry_pname,
365 archive_entry_uid(entry),
366 archive_entry_gid(entry)) != 0) {
368 "%s: failed "
369 "to set uid/gid to %"PRIu64":%"PRIu64" (%s)\n",
370 pkgver, archive_entry_uid(entry),
371 archive_entry_gid(entry),
372 strerror(errno));
373 } else {
374 xbps_dbg_printf("%s: entry %s changed "
375 "uid/gid to %"PRIu64":%"PRIu64".\n", pkgver, entry_pname,
376 archive_entry_uid(entry),
377 archive_entry_gid(entry));
378 }
379 }
380 /*
381 * Check if current file mode differs from file mode
382 * in binpkg and apply perms if true.
383 */
384 if (!force && file_exists && skip_extract &&
385 (archive_entry_mode(entry) != st.st_mode)) {
386 if (chmod(entry_pname,
387 archive_entry_mode(entry)) != 0) {
389 "%s: failed "
390 "to set perms %s to %s: %s\n",
391 pkgver, archive_entry_strmode(entry),
392 entry_pname,
393 strerror(errno));
394 rv = EINVAL;
395 goto out;
396 }
397 xbps_dbg_printf("%s: entry %s changed file "
398 "mode to %s.\n", pkgver, entry_pname,
399 archive_entry_strmode(entry));
400 }
401 if (!force && skip_extract) {
402 archive_read_data_skip(ar);
403 continue;
404 }
405 /*
406 * Reset entry_pname again because if entry's pathname
407 * has been changed it will become a dangling pointer.
408 */
409 entry_pname = archive_entry_pathname(entry);
410 /*
411 * Extract entry from archive.
412 */
413 if (archive_read_extract(ar, entry, flags) != 0) {
414 error = xbps_archive_errno(ar);
415 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL,
416 error, pkgver,
417 "%s: [unpack] failed to extract file `%s': %s",
418 pkgver, entry_pname, strerror(error));
419 break;
420 } else {
421 if (xhp->unpack_cb != NULL) {
422 xucd.entry = entry_pname;
423 xucd.entry_extract_count++;
424 (*xhp->unpack_cb)(&xucd, xhp->unpack_cb_data);
425 }
426 }
427 }
428 /*
429 * If there was any error extracting files from archive, error out.
430 */
431 if (error || ar_rv == ARCHIVE_FATAL) {
432 rv = error;
433 if (!rv)
434 rv = ar_rv;
435 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL, rv, pkgver,
436 "%s: [unpack] failed to extract files: %s",
437 pkgver, strerror(rv));
438 goto out;
439 }
440 /*
441 * Externalize binpkg files.plist to disk, if not empty.
442 */
443 if (xbps_dictionary_count(binpkg_filesd)) {
444 char *buf;
445 mode_t prev_umask;
446 prev_umask = umask(022);
447 buf = xbps_xasprintf("%s/.%s-files.plist", xhp->metadir, pkgname);
448 if (!xbps_dictionary_externalize_to_file(binpkg_filesd, buf)) {
449 rv = errno;
450 umask(prev_umask);
451 free(buf);
452 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL,
453 rv, pkgver, "%s: [unpack] failed to externalize pkg "
454 "pkg metadata files: %s", pkgver, strerror(rv));
455 goto out;
456 }
457 umask(prev_umask);
458 free(buf);
459 }
460out:
461 /*
462 * If unpacked pkg has no files, remove its files metadata plist.
463 */
464 if (!xbps_dictionary_count(binpkg_filesd)) {
465 char *buf = xbps_xasprintf("%s/.%s-files.plist", xhp->metadir, pkgname);
466 unlink(buf);
467 free(buf);
468 }
469 xbps_object_release(binpkg_filesd);
470
471 return rv;
472}
473
474int HIDDEN
475xbps_unpack_binary_pkg(struct xbps_handle *xhp, xbps_dictionary_t pkg_repod)
476{
477 char bpkg[PATH_MAX];
478 struct archive *ar = NULL;
479 struct stat st;
480 const char *pkgver;
481 ssize_t l;
482 int pkg_fd = -1, rv = 0;
483 mode_t myumask;
484
485 assert(xbps_object_type(pkg_repod) == XBPS_TYPE_DICTIONARY);
486
487 xbps_dictionary_get_cstring_nocopy(pkg_repod, "pkgver", &pkgver);
488 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK, 0, pkgver, NULL);
489
490 l = xbps_pkg_path(xhp, bpkg, sizeof(bpkg), pkg_repod);
491 if (l < 0) {
492 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL,
493 errno, pkgver,
494 "%s: [unpack] cannot determine binary package "
495 "file: %s", pkgver, strerror(errno));
496 return -l;
497 }
498
499 if ((ar = archive_read_new()) == NULL)
500 return ENOMEM;
501 /*
502 * Enable support for tar format and some compression methods.
503 */
504 archive_read_support_filter_gzip(ar);
505 archive_read_support_filter_bzip2(ar);
506 archive_read_support_filter_xz(ar);
507 archive_read_support_filter_lz4(ar);
508 archive_read_support_filter_zstd(ar);
509 archive_read_support_format_tar(ar);
510
511 myumask = umask(022);
512
513 pkg_fd = open(bpkg, O_RDONLY|O_CLOEXEC);
514 if (pkg_fd == -1) {
515 rv = errno;
516 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL,
517 rv, pkgver,
518 "%s: [unpack] failed to open binary package `%s': %s",
519 pkgver, bpkg, strerror(rv));
520 goto out;
521 }
522 if (fstat(pkg_fd, &st) == -1) {
523 rv = errno;
524 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL,
525 rv, pkgver,
526 "%s: [unpack] failed to fstat binary package `%s': %s",
527 pkgver, bpkg, strerror(rv));
528 goto out;
529 }
530 if (archive_read_open_fd(ar, pkg_fd, st.st_blksize) == ARCHIVE_FATAL) {
531 rv = xbps_archive_errno(ar);
532 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL,
533 rv, pkgver,
534 "%s: [unpack] failed to read binary package `%s': %s",
535 pkgver, bpkg, strerror(rv));
536 goto out;
537 }
538 /*
539 * Externalize pkg files dictionary to metadir.
540 */
541 if (access(xhp->metadir, R_OK|X_OK) == -1) {
542 rv = errno;
543 if (rv != ENOENT)
544 goto out;
545
546 if (xbps_mkpath(xhp->metadir, 0755) == -1) {
547 rv = errno;
548 goto out;
549 }
550 }
551 /*
552 * Extract archive files.
553 */
554 if ((rv = unpack_archive(xhp, pkg_repod, pkgver, bpkg, ar)) != 0) {
555 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL, rv, pkgver,
556 "%s: [unpack] failed to unpack files from archive: %s",
557 pkgver, strerror(rv));
558 goto out;
559 }
560 /*
561 * Set package state to unpacked.
562 */
563 if ((rv = xbps_set_pkg_state_dictionary(pkg_repod,
564 XBPS_PKG_STATE_UNPACKED)) != 0) {
565 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL,
566 rv, pkgver,
567 "%s: [unpack] failed to set state to unpacked: %s",
568 pkgver, strerror(rv));
569 }
570 /* register alternatives */
571 if ((rv = xbps_alternatives_register(xhp, pkg_repod)) != 0) {
572 xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL,
573 rv, pkgver,
574 "%s: [unpack] failed to register alternatives: %s",
575 pkgver, strerror(rv));
576 }
577
578out:
579 if (pkg_fd != -1)
580 close(pkg_fd);
581 if (ar != NULL)
582 archive_read_free(ar);
583
584 /* restore */
585 umask(myumask);
586
587 return rv;
588}
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
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:724
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