XBPS Library API  0.19
The X Binary Package System
package_unpack.c
1 /*-
2  * Copyright (c) 2008-2012 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 #ifdef HAVE_CONFIG_H
27 #include "config.h"
28 #endif
29 
30 #include <sys/stat.h>
31 #include <stdio.h>
32 #include <stdbool.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <errno.h>
36 #include <fcntl.h>
37 #include <unistd.h>
38 #include <libgen.h>
39 
40 #include "xbps_api_impl.h"
41 
42 static int
43 set_extract_flags(uid_t euid)
44 {
45  int flags;
46 
47  if (euid == 0)
48  flags = FEXTRACT_FLAGS;
49  else
50  flags = EXTRACT_FLAGS;
51 
52  return flags;
53 }
54 
55 static const char *
56 find_pkg_symlink_target(prop_dictionary_t d, const char *file)
57 {
58  prop_array_t links;
59  prop_object_t obj;
60  size_t i;
61  const char *pkgfile, *tgt = NULL;
62  char *rfile;
63 
64  assert(d);
65 
66  links = prop_dictionary_get(d, "links");
67  assert(links);
68 
69  for (i = 0; i < prop_array_count(links); i++) {
70  rfile = strchr(file, '.') + 1;
71  obj = prop_array_get(links, i);
72  prop_dictionary_get_cstring_nocopy(obj, "file", &pkgfile);
73  if (strcmp(rfile, pkgfile) == 0) {
74  prop_dictionary_get_cstring_nocopy(obj, "target", &tgt);
75  break;
76  }
77  }
78 
79  return tgt;
80 }
81 
82 static int
83 unpack_archive(struct xbps_handle *xhp,
84  prop_dictionary_t pkg_repod,
85  struct archive *ar)
86 {
87  prop_dictionary_t pkg_metad = NULL, filesd = NULL, old_filesd = NULL;
88  prop_array_t array, obsoletes;
89  prop_object_t obj;
90  prop_data_t data;
91  void *instbuf = NULL, *rembuf = NULL;
92  const struct stat *entry_statp;
93  struct stat st;
94  struct xbps_unpack_cb_data xucd;
95  struct archive_entry *entry;
96  size_t i, entry_idx = 0;
97  size_t instbufsiz, rembufsiz;
98  ssize_t entry_size;
99  const char *file, *entry_pname, *transact, *pkgname;
100  const char *version, *pkgver, *fname, *tgtlnk;
101  char *dname, *buf, *buf2, *p, *p2;
102  int ar_rv, rv, rv_stat, flags;
103  bool preserve, update, conf_file, file_exists, skip_obsoletes;
104  bool softreplace, skip_extract, force;
105  uid_t euid;
106 
107  assert(prop_object_type(pkg_repod) == PROP_TYPE_DICTIONARY);
108  assert(ar != NULL);
109 
110  force = preserve = update = conf_file = file_exists = false;
111  skip_obsoletes = softreplace = false;
112 
113  prop_dictionary_get_bool(pkg_repod, "preserve", &preserve);
114  prop_dictionary_get_bool(pkg_repod, "skip-obsoletes", &skip_obsoletes);
115  prop_dictionary_get_bool(pkg_repod, "softreplace", &softreplace);
116  prop_dictionary_get_cstring_nocopy(pkg_repod,
117  "transaction", &transact);
118  prop_dictionary_get_cstring_nocopy(pkg_repod, "pkgname", &pkgname);
119  prop_dictionary_get_cstring_nocopy(pkg_repod, "version", &version);
120  prop_dictionary_get_cstring_nocopy(pkg_repod, "pkgver", &pkgver);
121  prop_dictionary_get_cstring_nocopy(pkg_repod, "filename", &fname);
122 
123  euid = geteuid();
124 
125  if (xhp->flags & XBPS_FLAG_FORCE_INSTALL)
126  force = true;
127 
128  if (xhp->unpack_cb != NULL) {
129  /* initialize data for unpack cb */
130  memset(&xucd, 0, sizeof(xucd));
131  }
132  if (access(xhp->rootdir, R_OK) == -1) {
133  if (errno != ENOENT) {
134  rv = errno;
135  goto out;
136  }
137  if (xbps_mkpath(xhp->rootdir, 0750) == -1) {
138  rv = errno;
139  goto out;
140  }
141  }
142  if (chdir(xhp->rootdir) == -1) {
143  xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL,
144  errno, pkgname, version,
145  "%s: [unpack] failed to chdir to rootdir `%s': %s",
146  pkgver, xhp->rootdir, strerror(errno));
147  rv = errno;
148  goto out;
149  }
150  if (strcmp(transact, "update") == 0)
151  update = true;
152  /*
153  * Process the archive files.
154  */
155  for (;;) {
156  ar_rv = archive_read_next_header(ar, &entry);
157  if (ar_rv == ARCHIVE_EOF || ar_rv == ARCHIVE_FATAL)
158  break;
159  else if (ar_rv == ARCHIVE_RETRY)
160  continue;
161 
162  entry_statp = archive_entry_stat(entry);
163  entry_pname = archive_entry_pathname(entry);
164  entry_size = archive_entry_size(entry);
165  flags = set_extract_flags(euid);
166  /*
167  * Ignore directories from archive.
168  */
169  if (S_ISDIR(entry_statp->st_mode)) {
170  archive_read_data_skip(ar);
171  continue;
172  }
173  /*
174  * Prepare unpack callback ops.
175  */
176  if (xhp->unpack_cb != NULL) {
177  xucd.xhp = xhp;
178  xucd.pkgver = pkgver;
179  xucd.entry = entry_pname;
180  xucd.entry_size = entry_size;
181  xucd.entry_is_conf = false;
182  }
183  if (strcmp("./INSTALL", entry_pname) == 0) {
184  /*
185  * Store file in a buffer and execute
186  * the "pre" action from it.
187  */
188  instbufsiz = entry_size;
189  instbuf = malloc(entry_size);
190  assert(instbuf);
191 
192  if (archive_read_data(ar, instbuf, entry_size) !=
193  entry_size) {
194  rv = EINVAL;
195  free(instbuf);
196  goto out;
197  }
198 
199  rv = xbps_pkg_exec_buffer(xhp, instbuf, instbufsiz,
200  pkgname, version, "pre", update);
201  if (rv != 0) {
202  xbps_set_cb_state(xhp,
203  XBPS_STATE_UNPACK_FAIL,
204  rv, pkgname, version,
205  "%s: [unpack] INSTALL script failed "
206  "to execute pre ACTION: %s",
207  pkgver, strerror(rv));
208  free(instbuf);
209  goto out;
210  }
211  continue;
212 
213  } else if (strcmp("./REMOVE", entry_pname) == 0) {
214  /* store file in a buffer */
215  rembufsiz = entry_size;
216  rembuf = malloc(entry_size);
217  assert(rembuf);
218  if (archive_read_data(ar, rembuf, entry_size) !=
219  entry_size) {
220  rv = EINVAL;
221  free(rembuf);
222  goto out;
223  }
224  continue;
225 
226  } else if (strcmp("./files.plist", entry_pname) == 0) {
227  /*
228  * Internalize this entry into a prop_dictionary
229  * to check for obsolete files if updating a package.
230  * It will be extracted to disk at the end.
231  */
232  filesd = xbps_dictionary_from_archive_entry(ar, entry);
233  if (filesd == NULL) {
234  rv = errno;
235  goto out;
236  }
237  continue;
238  } else if (strcmp("./props.plist", entry_pname) == 0) {
239  /* ignore this one; we use pkg data from repo index */
240  archive_read_data_skip(ar);
241  continue;
242  }
243  /*
244  * If XBPS_PKGFILES or XBPS_PKGPROPS weren't found
245  * in the archive at this phase, skip all data.
246  */
247  if (filesd == NULL) {
248  archive_read_data_skip(ar);
249  /*
250  * If we have processed 4 entries and the two
251  * required metadata files weren't found, bail out.
252  * This is not an XBPS binary package.
253  */
254  if (entry_idx >= 3) {
255  xbps_set_cb_state(xhp,
256  XBPS_STATE_UNPACK_FAIL,
257  ENODEV, pkgname, version,
258  "%s: [unpack] invalid binary package `%s'.",
259  pkgver, fname);
260  rv = ENODEV;
261  goto out;
262  }
263 
264  entry_idx++;
265  continue;
266  }
267  /*
268  * Compute total entries in progress data, if set.
269  * total_entries = files + conf_files + links.
270  */
271  if (xhp->unpack_cb != NULL) {
272  xucd.entry_total_count = 0;
273  array = prop_dictionary_get(filesd, "files");
274  xucd.entry_total_count +=
275  (ssize_t)prop_array_count(array);
276  array = prop_dictionary_get(filesd, "conf_files");
277  xucd.entry_total_count +=
278  (ssize_t)prop_array_count(array);
279  array = prop_dictionary_get(filesd, "links");
280  xucd.entry_total_count +=
281  (ssize_t)prop_array_count(array);
282  }
283  /*
284  * Always check that extracted file exists and hash
285  * doesn't match, in that case overwrite the file.
286  * Otherwise skip extracting it.
287  */
288  conf_file = skip_extract = file_exists = false;
289  rv_stat = lstat(entry_pname, &st);
290  if (rv_stat == 0)
291  file_exists = true;
292 
293  if (!force && S_ISREG(entry_statp->st_mode)) {
294  buf = strchr(entry_pname, '.') + 1;
295  assert(buf != NULL);
296  if (file_exists) {
297  /*
298  * Handle configuration files. Check if current
299  * entry is a configuration file and take action
300  * if required. Skip packages that don't have
301  * "conf_files" array on its XBPS_PKGPROPS
302  * dictionary.
303  */
304  if (xbps_entry_is_a_conf_file(pkg_repod, buf)) {
305  conf_file = true;
306  if (xhp->unpack_cb != NULL)
307  xucd.entry_is_conf = true;
308 
309  rv = xbps_entry_install_conf_file(xhp,
310  filesd, entry, entry_pname,
311  pkgname, version);
312  if (rv == -1) {
313  /* error */
314  goto out;
315  } else if (rv == 0) {
316  /*
317  * Keep curfile as is.
318  */
319  skip_extract = true;
320  }
321  } else {
322  rv = xbps_file_hash_check_dictionary(
323  xhp, filesd, "files", buf);
324  if (rv == -1) {
325  /* error */
326  xbps_dbg_printf(xhp,
327  "%s-%s: failed to check"
328  " hash for `%s': %s\n",
329  pkgname, version,
330  entry_pname,
331  strerror(errno));
332  goto out;
333  } else if (rv == 0) {
334  /*
335  * hash match, skip extraction.
336  */
337  xbps_dbg_printf(xhp,
338  "%s-%s: file %s "
339  "matches existing SHA256, "
340  "skipping...\n", pkgname,
341  version, entry_pname);
342  skip_extract = true;
343  }
344  }
345  }
346  } else if (!force && S_ISLNK(entry_statp->st_mode)) {
347  /*
348  * Check if current link from binpkg hasn't been
349  * modified, otherwise extract new link.
350  */
351  buf = realpath(entry_pname, NULL);
352  if (buf) {
353  if (strcmp(xhp->rootdir, "/")) {
354  p = buf;
355  p += strlen(xhp->rootdir);
356  } else
357  p = buf;
358  tgtlnk = find_pkg_symlink_target(filesd,
359  entry_pname);
360  assert(tgtlnk);
361  if (strncmp(tgtlnk, "./", 2) == 0) {
362  buf2 = strdup(entry_pname);
363  assert(buf2);
364  dname = dirname(buf2);
365  p2 = xbps_xasprintf("%s/%s", dname, tgtlnk);
366  free(buf2);
367  } else {
368  p2 = strdup(tgtlnk);
369  assert(p2);
370  }
371  xbps_dbg_printf(xhp, "%s: symlink %s cur: %s "
372  "new: %s\n", pkgver, entry_pname, p, p2);
373 
374  if (strcmp(p, p2) == 0) {
375  xbps_dbg_printf(xhp, "%s-%s: symlink "
376  "%s matched, skipping...\n",
377  pkgname, version, entry_pname);
378  skip_extract = true;
379  }
380  free(buf);
381  }
382  }
383  /*
384  * Check if current file mode differs from file mode
385  * in binpkg and apply perms if true.
386  */
387  if (!force && file_exists && skip_extract &&
388  (entry_statp->st_mode != st.st_mode)) {
389  if (chmod(entry_pname,
390  entry_statp->st_mode) != 0) {
391  xbps_dbg_printf(xhp,
392  "%s-%s: failed "
393  "to set perms %s to %s: %s\n",
394  pkgname, version,
395  archive_entry_strmode(entry),
396  entry_pname,
397  strerror(errno));
398  rv = EINVAL;
399  goto out;
400  }
401  xbps_dbg_printf(xhp, "%s-%s: entry %s changed file "
402  "mode to %s.\n", pkgname, version, entry_pname,
403  archive_entry_strmode(entry));
404  }
405  /*
406  * Check if current uid/gid differs from file in binpkg,
407  * and change permissions if true.
408  */
409  if ((!force && file_exists && skip_extract && (euid == 0)) &&
410  (((entry_statp->st_uid != st.st_uid)) ||
411  ((entry_statp->st_gid != st.st_gid)))) {
412  if (chown(entry_pname,
413  entry_statp->st_uid, entry_statp->st_gid) != 0) {
414  xbps_dbg_printf(xhp,
415  "%s-%s: failed "
416  "to set uid/gid to %u:%u (%s)\n",
417  pkgname, version,
418  entry_statp->st_uid, entry_statp->st_gid,
419  strerror(errno));
420  } else {
421  xbps_dbg_printf(xhp, "%s-%s: entry %s changed "
422  "uid/gid to %u:%u.\n", pkgname, version,
423  entry_pname,
424  entry_statp->st_uid, entry_statp->st_gid);
425  }
426  }
427 
428  if (!update && conf_file && file_exists && !skip_extract) {
429  /*
430  * If installing new package preserve old configuration
431  * file but renaming it to <file>.old.
432  */
433  buf = xbps_xasprintf("%s.old", entry_pname);
434  (void)rename(entry_pname, buf);
435  free(buf);
436  buf = NULL;
437  xbps_set_cb_state(xhp,
438  XBPS_STATE_CONFIG_FILE, 0,
439  pkgname, version,
440  "Renamed old configuration file "
441  "`%s' to `%s.old'.", entry_pname, entry_pname);
442  }
443 
444  if (!force && skip_extract) {
445  archive_read_data_skip(ar);
446  continue;
447  }
448  /*
449  * Reset entry_pname again because if entry's pathname
450  * has been changed it will become a dangling pointer.
451  */
452  entry_pname = archive_entry_pathname(entry);
453  /*
454  * Extract entry from archive.
455  */
456  if (archive_read_extract(ar, entry, flags) != 0) {
457  rv = archive_errno(ar);
458  xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL,
459  rv, pkgname, version,
460  "%s: [unpack] failed to extract file `%s': %s",
461  pkgver, entry_pname, strerror(rv));
462  } else {
463  if (xhp->unpack_cb != NULL) {
464  xucd.entry_extract_count++;
465  (*xhp->unpack_cb)(&xucd, xhp->unpack_cb_data);
466  }
467  }
468  }
469  /*
470  * If there was any error extracting files from archive, error out.
471  */
472  if ((rv = archive_errno(ar)) != 0) {
473  xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL,
474  rv, pkgname, version,
475  "%s: [unpack] failed to extract files: %s",
476  pkgver, fname, archive_error_string(ar));
477  goto out;
478  }
479  /*
480  * Skip checking for obsolete files on:
481  * - New package installation without "softreplace" keyword.
482  * - Package with "preserve" keyword.
483  * - Package with "skip-obsoletes" keyword.
484  */
485 
486  if (skip_obsoletes || preserve || (!softreplace && !update))
487  goto out1;
488  /*
489  * Check and remove obsolete files on:
490  * - Package upgrade.
491  * - Package with "softreplace" keyword.
492  */
493  old_filesd = xbps_pkgdb_get_pkg_metadata(xhp, pkgname);
494  assert(prop_object_type(old_filesd) == PROP_TYPE_DICTIONARY);
495 
496  obsoletes = xbps_find_pkg_obsoletes(xhp, old_filesd, filesd);
497  for (i = 0; i < prop_array_count(obsoletes); i++) {
498  obj = prop_array_get(obsoletes, i);
499  file = prop_string_cstring_nocopy(obj);
500  if (remove(file) == -1) {
501  xbps_set_cb_state(xhp,
502  XBPS_STATE_REMOVE_FILE_OBSOLETE_FAIL,
503  errno, pkgname, version,
504  "%s: failed to remove obsolete entry `%s': %s",
505  pkgver, file, strerror(errno));
506  continue;
507  }
508  xbps_set_cb_state(xhp,
509  XBPS_STATE_REMOVE_FILE_OBSOLETE,
510  0, pkgname, version,
511  "%s: removed obsolete entry: %s", pkgver, file);
512  prop_object_release(obj);
513  }
514 
515 out1:
516  prop_dictionary_make_immutable(pkg_repod);
517  pkg_metad = prop_dictionary_copy_mutable(pkg_repod);
518 
519  /* Add objects from XBPS_PKGFILES */
520  array = prop_dictionary_get(filesd, "files");
521  if (array && prop_array_count(array))
522  prop_dictionary_set(pkg_metad, "files", array);
523  array = prop_dictionary_get(filesd, "conf_files");
524  if (array && prop_array_count(array))
525  prop_dictionary_set(pkg_metad, "conf_files", array);
526  array = prop_dictionary_get(filesd, "links");
527  if (array && prop_array_count(array))
528  prop_dictionary_set(pkg_metad, "links", array);
529  array = prop_dictionary_get(filesd, "dirs");
530  if (array && prop_array_count(array))
531  prop_dictionary_set(pkg_metad, "dirs", array);
532 
533  /* Add install/remove scripts data objects */
534  if (instbuf != NULL) {
535  data = prop_data_create_data(instbuf, instbufsiz);
536  assert(data);
537  prop_dictionary_set(pkg_metad, "install-script", data);
538  prop_object_release(data);
539  free(instbuf);
540  }
541  if (rembuf != NULL) {
542  data = prop_data_create_data(rembuf, rembufsiz);
543  assert(data);
544  prop_dictionary_set(pkg_metad, "remove-script", data);
545  prop_object_release(data);
546  free(rembuf);
547  }
548  /* Remove unneeded objs from transaction */
549  prop_dictionary_remove(pkg_metad, "remove-and-update");
550  prop_dictionary_remove(pkg_metad, "transaction");
551  prop_dictionary_remove(pkg_metad, "state");
552 
553  /*
554  * Externalize pkg dictionary to metadir.
555  */
556  if (access(xhp->metadir, R_OK|X_OK) == -1) {
557  if (errno == ENOENT) {
558  xbps_mkpath(xhp->metadir, 0755);
559  } else {
560  rv = errno;
561  goto out;
562  }
563  }
564  buf = xbps_xasprintf("%s/.%s.plist", XBPS_META_PATH, pkgname);
565  if (!prop_dictionary_externalize_to_file(pkg_metad, buf)) {
566  rv = errno;
567  xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL,
568  errno, pkgname, version,
569  "%s: [unpack] failed to extract metadata file `%s': %s",
570  pkgver, buf, strerror(errno));
571  free(buf);
572  goto out;
573  }
574  free(buf);
575 out:
576  if (prop_object_type(pkg_metad) == PROP_TYPE_DICTIONARY)
577  prop_object_release(pkg_metad);
578  if (prop_object_type(filesd) == PROP_TYPE_DICTIONARY)
579  prop_object_release(filesd);
580 
581  return rv;
582 }
583 
584 int HIDDEN
585 xbps_unpack_binary_pkg(struct xbps_handle *xhp, prop_dictionary_t pkg_repod)
586 {
587  struct archive *ar = NULL;
588  struct stat pkg_stat;
589  const char *pkgname, *version, *pkgver;
590  char *bpkg;
591  int pkg_fd, rv = 0;
592 
593  assert(prop_object_type(pkg_repod) == PROP_TYPE_DICTIONARY);
594 
595  prop_dictionary_get_cstring_nocopy(pkg_repod, "pkgname", &pkgname);
596  prop_dictionary_get_cstring_nocopy(pkg_repod, "version", &version);
597  prop_dictionary_get_cstring_nocopy(pkg_repod, "pkgver", &pkgver);
598 
599  xbps_set_cb_state(xhp, XBPS_STATE_UNPACK, 0, pkgname, version, NULL);
600 
601  bpkg = xbps_repository_pkg_path(xhp, pkg_repod);
602  if (bpkg == NULL) {
603  xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL,
604  errno, pkgname, version,
605  "%s: [unpack] cannot determine binary package "
606  "file for `%s': %s", pkgver, bpkg, strerror(errno));
607  return errno;
608  }
609 
610  if ((ar = archive_read_new()) == NULL) {
611  free(bpkg);
612  return ENOMEM;
613  }
614  /*
615  * Enable support for tar format and gzip/bzip2/lzma compression methods.
616  */
617  archive_read_support_compression_gzip(ar);
618  archive_read_support_compression_bzip2(ar);
619  archive_read_support_compression_xz(ar);
620  archive_read_support_format_tar(ar);
621 
622  pkg_fd = open(bpkg, O_RDONLY|O_CLOEXEC);
623  if (pkg_fd == -1) {
624  rv = archive_errno(ar);
625  xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL,
626  rv, pkgname, version,
627  "%s: [unpack] failed to open binary package `%s': %s",
628  pkgver, bpkg, strerror(rv));
629  free(bpkg);
630  archive_read_free(ar);
631  return rv;
632  }
633  archive_read_open_fd(ar, pkg_fd, ARCHIVE_READ_BLOCKSIZE);
634  free(bpkg);
635 
636 #ifdef HAVE_POSIX_FADVISE
637  fstat(pkg_fd, &pkg_stat);
638  posix_fadvise(pkg_fd, 0, pkg_stat.st_size, POSIX_FADV_SEQUENTIAL);
639 #endif
640  /*
641  * Extract archive files.
642  */
643  if ((rv = unpack_archive(xhp, pkg_repod, ar)) != 0) {
644  xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL,
645  rv, pkgname, version,
646  "%s: [unpack] failed to unpack files from archive: %s",
647  pkgver, strerror(rv));
648  goto out;
649  }
650 #ifdef HAVE_POSIX_FADVISE
651  posix_fadvise(pkg_fd, 0, pkg_stat.st_size, POSIX_FADV_DONTNEED);
652 #endif
653  /*
654  * Set package state to unpacked.
655  */
656  if ((rv = xbps_set_pkg_state_installed(xhp, pkgname, version,
657  XBPS_PKG_STATE_UNPACKED)) != 0) {
658  xbps_set_cb_state(xhp, XBPS_STATE_UNPACK_FAIL,
659  rv, pkgname, version,
660  "%s: [unpack] failed to set state to unpacked: %s",
661  pkgver, strerror(rv));
662  }
663 out:
664  close(pkg_fd);
665  if (ar)
666  archive_read_free(ar);
667 
668  return rv;
669 }