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