From 8c2ccea4357ec1cc1070ed822e708187917fe4ce Mon Sep 17 00:00:00 2001
From: Juan RP <xtraeme@gmail.com>
Date: Fri, 21 Jan 2011 17:16:58 +0100
Subject: [PATCH] WIP checkpoint for progress callbacks in the API.

At this moment I've only implemented the part for unpacking, other parts
will also use progress callbacks.

--HG--
branch : progress_callback
---
 bin/xbps-bin/install.c |  59 ++++++-
 include/xbps_api.h     |  67 +++++++-
 lib/initend.c          |  11 ++
 lib/package_unpack.c   | 355 ++++++++++++++++++++++++++---------------
 4 files changed, 357 insertions(+), 135 deletions(-)

diff --git a/bin/xbps-bin/install.c b/bin/xbps-bin/install.c
index fc73a455..5ffb3672 100644
--- a/bin/xbps-bin/install.c
+++ b/bin/xbps-bin/install.c
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2009-2010 Juan Romero Pardines.
+ * Copyright (c) 2009-2011 Juan Romero Pardines.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -31,8 +31,10 @@
 #include <ctype.h>
 #include <assert.h>
 #include <unistd.h>
+#include <limits.h>
 
 #include <xbps_api.h>
+#include "strlcpy.h"
 #include "defs.h"
 #include "../xbps-repo/defs.h"
 
@@ -421,14 +423,47 @@ replace_packages(prop_dictionary_t trans_dict, prop_dictionary_t pkgd,
 	return 0;
 }
 
+static void
+unpack_progress_cb_verbose(void *data)
+{
+	struct xbps_progress_data *xpd = data;
+
+	if (xpd->entry == NULL || xpd->entry_is_metadata)
+		return;
+	else if (xpd->entry_size <= 0)
+		return;
+
+	fprintf(stderr, "Extracted %sfile `%s' (%" PRIi64 " bytes)\n",
+	    xpd->entry_is_conf ? "configuration " : "", xpd->entry,
+	    xpd->entry_size);
+}
+
+static void
+unpack_progress_cb_percentage(void *data)
+{
+	struct xbps_progress_data *xpd = data;
+	int percent = 0;
+
+	if (xpd->entry_is_metadata)
+		return;
+
+	percent =
+	    (int)((xpd->entry_extract_count * 100) / xpd->entry_total_count);
+	if (percent > 100)
+		percent = 100;
+
+	printf("\033[s(%3d%%)\033[u", percent);
+}
+
 static int
 exec_transaction(struct transaction *trans)
 {
 	prop_dictionary_t instpkgd;
 	prop_object_t obj;
 	prop_object_iterator_t replaces_iter;
-	const char *pkgname, *version, *pkgver, *instver, *filename, *tract;
-	int rv = 0;
+	struct xbps_progress_data xpd;
+	const char *pkgname, *version, *pkgver, *instver, *filen, *tract;
+	int flags = xbps_get_flags(), rv = 0;
 	bool update, preserve, autoinst;
 	pkg_state_t state = 0;
 
@@ -470,13 +505,13 @@ exec_transaction(struct transaction *trans)
 		prop_dictionary_get_cstring_nocopy(obj, "pkgname", &pkgname);
 		prop_dictionary_get_cstring_nocopy(obj, "version", &version);
 		prop_dictionary_get_cstring_nocopy(obj, "pkgver", &pkgver);
-		prop_dictionary_get_cstring_nocopy(obj, "filename", &filename);
+		prop_dictionary_get_cstring_nocopy(obj, "filename", &filen);
 		prop_dictionary_get_cstring_nocopy(obj, "trans-action", &tract);
 
 		assert(pkgname != NULL);
 		assert(version != NULL);
 		assert(pkgver != NULL);
-		assert(filename != NULL);
+		assert(filen != NULL);
 		assert(tract != NULL);
 
 		prop_dictionary_get_bool(obj, "automatic-install", &autoinst);
@@ -536,12 +571,24 @@ exec_transaction(struct transaction *trans)
 		/*
 		 * Unpack binary package.
 		 */
-		printf("Unpacking %s (from .../%s) ...\n", pkgver, filename);
+		printf("Unpacking `%s' (from ../%s) ... ", pkgver, filen);
+
+		if (flags & XBPS_FLAG_VERBOSE) {
+			xbps_unpack_binary_pkg_set_progress_cb(
+			    unpack_progress_cb_verbose, &xpd);
+			printf("\n");
+		} else {
+			xbps_unpack_binary_pkg_set_progress_cb(
+			    unpack_progress_cb_percentage, &xpd);
+		}
 		if ((rv = xbps_unpack_binary_pkg(obj)) != 0) {
 			fprintf(stderr, "xbps-bin: error unpacking %s "
 			    "(%s)\n", pkgver, strerror(rv));
 			return rv;
 		}
+		if ((flags & XBPS_FLAG_VERBOSE) == 0)
+			printf("\n");
+
 		/*
 		 * Register binary package.
 		 */
diff --git a/include/xbps_api.h b/include/xbps_api.h
index dca262f0..221f6bf5 100644
--- a/include/xbps_api.h
+++ b/include/xbps_api.h
@@ -30,6 +30,8 @@
 #include <stdio.h>
 #include <inttypes.h>
 
+#include <archive.h>
+#include <archive_entry.h>
 #include <prop/proplib.h>
 
 #ifdef  __cplusplus
@@ -119,6 +121,7 @@ __BEGIN_DECLS
 
 void		xbps_dbg_printf(const char *, ...);
 void		xbps_dbg_printf_append(const char *, ...);
+void		xbps_error_printf(const char *, ...);
 
 /** @addtogroup initend */ 
 /*@{*/
@@ -857,18 +860,78 @@ int xbps_set_pkg_state_dictionary(prop_dictionary_t dict, pkg_state_t state);
 
 /*@}*/
 
+/** @addtogroup unpack */
+
+/**
+ * @struct xbps_progress_kdata xbps_api.h "xbps_api.h"
+ * @brief Structure to be passed to the unpacking progress function callback.
+ */
+struct xbps_progress_data {
+	/*
+	 * @var entry
+	 *
+	 * Entry pathname string (set internally).
+	 */
+	const char *entry;
+	/*
+	 * @var[out] entry_size
+	 *
+	 * Entry file size (set internally).
+	 */
+	int64_t entry_size;
+	/*
+	 * @var[out] entry_extract_count;
+	 *
+	 * Total number of extracted entries (set internally).
+	 */
+	ssize_t entry_extract_count;
+	/*
+	 * @var[out] entry_total_count
+	 *
+	 * Total number of entries in package (set internally).
+	 */
+	ssize_t entry_total_count;
+	/*
+	 * @var[out] entry_is_metadata
+	 *
+	 * If true "entry" is a package metadata file (set internally).
+	 */
+	bool entry_is_metadata;
+	/*
+	 * @var[out] entry_is_conf
+	 *
+	 * If true "entry" is a configuration file (set internally).
+	 */
+	bool entry_is_conf;
+};
+
 /**
- * @ingroup unpack
- *
  * Unpacks a binary package into specified root directory.
  *
  * @param[in] trans_pkg_dict Package proplib dictionary as stored in the
  * \a packages array returned by the transaction dictionary.
+ * @param[in] Pointer to a function callback to update progress data
+ * while extracting files in package (optional).
+ * @param[in] Pointer to a struct xbps_progress_data to be passed to
+ * the function callback \a unpack_progress_cb.
  *
  * @return 0 on success, otherwise an errno value.
  */
 int xbps_unpack_binary_pkg(prop_dictionary_t trans_pkg_dict);
 
+/**
+ * Sets a pointer to a function callback to update the unpack
+ * progress while \a xbps_unpack_binary_pkg is running.
+ *
+ * @param[in] fn Pointer to the function callback.
+ * @param[in] xpd Pointer to a "xbps_progress_data" structure
+ * to be passed as argument to the function callback.
+ */
+void xbps_unpack_binary_pkg_set_progress_cb(void (*fn)(void *),
+					    struct xbps_progress_data *xpd);
+
+/*@}*/
+
 /** @addtogroup util */
 /*@{*/
 
diff --git a/lib/initend.c b/lib/initend.c
index 645ebf71..cd52616a 100644
--- a/lib/initend.c
+++ b/lib/initend.c
@@ -76,3 +76,14 @@ xbps_dbg_printf_append(const char *fmt, ...)
 	vfprintf(stderr, fmt, ap);
 	va_end(ap);
 }
+
+void
+xbps_error_printf(const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	fprintf(stderr, "ERROR: ");
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+}
diff --git a/lib/package_unpack.c b/lib/package_unpack.c
index 959aa580..4b692e2b 100644
--- a/lib/package_unpack.c
+++ b/lib/package_unpack.c
@@ -1,5 +1,5 @@
 /*-
- * Copyright (c) 2008-2010 Juan Romero Pardines.
+ * Copyright (c) 2008-2011 Juan Romero Pardines.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -65,6 +65,9 @@
  * data type is specified on its edge, i.e string, array, integer, dictionary.
  */
 
+static void (*unpack_progress_cb)(void *);
+static struct xbps_progress_data *sp_xpd;
+
 static void
 set_extract_flags(int *flags, bool update)
 {
@@ -86,22 +89,93 @@ set_extract_flags(int *flags, bool update)
 	*flags = lflags;
 }
 
+static int
+extract_metafile(struct archive *ar,
+		 struct archive_entry *entry,
+		 const char *file,
+		 const char *pkgname,
+		 const char *version,
+		 bool exec,
+		 int flags)
+{
+	char *buf;
+	int rv;
+
+	buf = xbps_xasprintf(".%s/metadata/%s/%s",
+	    XBPS_META_PATH, pkgname, file);
+	if (buf == NULL)
+		return ENOMEM;
+
+	archive_entry_set_pathname(entry, buf);
+	free(buf);
+	if (exec)
+		archive_entry_set_perm(entry, 0750);
+
+	if (archive_read_extract(ar, entry, flags) != 0) {
+		if ((rv = archive_errno(ar)) != EEXIST) {
+			xbps_error_printf("failed to extract metadata file `%s'"
+			    "for `%s-%s': %s\n", file, pkgname, version,
+			    strerror(rv));
+		}
+	}
+
+	return 0;
+}
+
+static int
+remove_metafile(const char *file, const char *pkgname, const char *version)
+{
+	char *buf;
+
+	buf = xbps_xasprintf(".%s/metadata/%s/%s",
+	    XBPS_META_PATH, file, pkgname);
+	if (buf == NULL)
+		return ENOMEM;
+
+	if (unlink(buf) == -1) {
+		if (errno && errno != ENOENT) {
+			xbps_error_printf("failed to remove metadata file "
+			    "`%s' while unpacking `%s-%s': %s\n", file,
+			    pkgname, version, strerror(errno));
+			free(buf);
+			return errno;
+		}
+	}
+	free(buf);
+
+	return 0;
+}
+
+/*
+ * Execute the unpack progress function callback if set and its
+ * private data is also set. It's so sad that
+ * archive_read_set_progress_callback() from libarchive(3) cannot be used
+ * here because sometimes it misses some entries by unknown reasons.
+ */
+#define RUN_PROGRESS_CB()				\
+do {							\
+	if (unpack_progress_cb != NULL && xpd != NULL)	\
+		(*unpack_progress_cb)(xpd);		\
+} while (0)
+
 /*
  * TODO: remove printfs and return appropiate errors to be interpreted by
  * the consumer.
  */
 static int
-unpack_archive_fini(prop_dictionary_t pkg_repod,
-		    struct archive *ar,
-		    const char *pkgname,
-		    const char *version)
+unpack_archive(prop_dictionary_t pkg_repod,
+	       struct archive *ar,
+	       struct xbps_progress_data *xpd,
+	       const char *pkgname,
+	       const char *version)
 {
 	prop_dictionary_t propsd, filesd, old_filesd;
+	prop_array_t array;
 	struct archive_entry *entry;
 	size_t entry_idx = 0;
 	const char *rootdir, *entry_pname, *transact;
 	char *buf;
-	int rv, flags, lflags;
+	int rv, flags;
 	bool preserve, update, replace_files_in_pkg_update;
 
 	assert(ar != NULL);
@@ -109,12 +183,14 @@ unpack_archive_fini(prop_dictionary_t pkg_repod,
 	assert(pkgname != NULL);
 	assert(version != NULL);
 
-	preserve = update = replace_files_in_pkg_update = false;
+	preserve = update = false;
 	rootdir = xbps_get_rootdir();
-	flags = xbps_get_flags();
 
-	if (chdir(rootdir) == -1)
+	if (chdir(rootdir) == -1) {
+		xbps_error_printf("cannot chdir to rootdir for "
+		    "`%s-%s': %s\n", pkgname, version, strerror(errno));
 		return errno;
+	}
 
 	prop_dictionary_get_bool(pkg_repod, "preserve", &preserve);
 	prop_dictionary_get_cstring_nocopy(pkg_repod,
@@ -130,43 +206,30 @@ unpack_archive_fini(prop_dictionary_t pkg_repod,
 	 * anymore.
 	 */
 	if (update) {
-		buf = xbps_xasprintf(".%s/metadata/%s/INSTALL",
-		    XBPS_META_PATH, pkgname);
-		if (buf == NULL)
-			return ENOMEM;
-
-		if (unlink(buf) == -1) {
-			if (errno && errno != ENOENT) {
-				free(buf);
-				return errno;
-			}
-		}
-		free(buf);
-
-		buf = xbps_xasprintf(".%s/metadata/%s/REMOVE",
-		    XBPS_META_PATH, pkgname);
-		if (buf == NULL)
-			return ENOMEM;
-
-		if (unlink(buf) == -1) {
-			if (errno && errno != ENOENT) {
-				free(buf);
-				return errno;
-			}
-		}
-		free(buf);
+		if ((rv = remove_metafile("INSTALL", pkgname, version)) != 0)
+			return rv;
+		if ((rv = remove_metafile("REMOVE", pkgname, version)) != 0)
+			return rv;
 	}
 	/*
 	 * Process the archive files.
 	 */
 	while (archive_read_next_header(ar, &entry) == ARCHIVE_OK) {
 		entry_pname = archive_entry_pathname(entry);
-		set_extract_flags(&lflags, update);
+		set_extract_flags(&flags, update);
+		if (unpack_progress_cb != NULL && xpd != NULL) {
+			xpd->entry = entry_pname;
+			xpd->entry_size = archive_entry_size(entry);
+			xpd->entry_is_metadata = false;
+			xpd->entry_is_conf = false;
+		}
 
 		if (strcmp("./INSTALL", entry_pname) == 0) {
+			if (xpd != NULL)
+				xpd->entry_is_metadata = true;
 			/*
-			 * Extract package INSTALL file into destination
-			 * directory and execute the pre install action.
+			 * Extract the INSTALL script first to execute
+			 * the pre install target.
 			 */
 			buf = xbps_xasprintf(".%s/metadata/%s/INSTALL",
 			    XBPS_META_PATH, pkgname);
@@ -174,85 +237,73 @@ unpack_archive_fini(prop_dictionary_t pkg_repod,
 				rv = ENOMEM;
 				goto out;
 			}
-			archive_entry_set_pathname(entry, buf);
-			archive_entry_set_perm(entry, 0750);
-			if (archive_read_extract(ar, entry, lflags) != 0) {
-				if ((rv = archive_errno(ar)) != EEXIST) {
-					free(buf);
-					goto out;
-				}
+			rv = extract_metafile(ar, entry, "INSTALL",
+			    pkgname, version, true, flags);
+			if (rv != 0) {
+				free(buf);
+				goto out;
 			}
 			rv = xbps_file_exec(buf, "pre",
 			     pkgname, version, update ? "yes" : "no", NULL);
 			free(buf);
 			if (rv != 0) {
-				fprintf(stderr,
-				    "%s: preinst action target error %s\n",
-				    pkgname, strerror(errno));
+				xbps_error_printf("%s-%s: pre-install script "
+				    "error: %s\n", pkgname, version,
+				    strerror(rv));
 				goto out;
 			}
-			/* Pass to the next entry if successful */
 			entry_idx++;
+			RUN_PROGRESS_CB();
 			continue;
-		/*
-		 * Unpack metadata files in final directory.
-		 */
+
 		} else if (strcmp("./REMOVE", entry_pname) == 0) {
-			buf = xbps_xasprintf(".%s/metadata/%s/REMOVE",
-			    XBPS_META_PATH, pkgname);
-			if (buf == NULL) {
-				rv = ENOMEM;
+			if (xpd != NULL)
+				xpd->entry_is_metadata = true;
+
+			rv = extract_metafile(ar, entry, "REMOVE",
+			    pkgname, version, true, flags);
+			if (rv != 0)
 				goto out;
-			}
-			archive_entry_set_pathname(entry, buf);
-			free(buf);
-			archive_entry_set_perm(entry, 0750);
-			if (archive_read_extract(ar, entry, lflags) != 0) {
-				if ((rv = archive_errno(ar)) != EEXIST)
-					goto out;
-			}
-			/* Pass to next entry if successful */
+
 			entry_idx++;
+			RUN_PROGRESS_CB();
 			continue;
 
 		} else if (strcmp("./files.plist", entry_pname) == 0) {
+			if (xpd != NULL)
+				xpd->entry_is_metadata = true;
+
 			/*
-			 * Now we have a dictionary from the entry
-			 * in memory. Will be written to disk later, when
-			 * all files are extracted.
+			 * Internalize this entry into a prop_dictionary
+			 * to check for obsolete files if updating a package.
+			 * It will be extracted to disk at the end.
 			 */
 			filesd = xbps_read_dict_from_archive_entry(ar, entry);
 			if (filesd == NULL) {
 				rv = errno;
 				goto out;
 			}
-			/* Pass to next entry */
 			entry_idx++;
+			RUN_PROGRESS_CB();
 			continue;
 
 		} else if (strcmp("./props.plist", entry_pname) == 0) {
-			buf = xbps_xasprintf(".%s/metadata/%s/%s",
-			    XBPS_META_PATH, pkgname, XBPS_PKGPROPS);
-			if (buf == NULL) {
-				rv = ENOMEM;
-				goto out;
-			}
-			archive_entry_set_pathname(entry, buf);
-			free(buf);
-			if (archive_read_extract(ar, entry, lflags) != 0) {
-				rv = archive_errno(ar);
-				goto out;
-			}
+			if (xpd != NULL)
+				xpd->entry_is_metadata = true;
 
-			propsd =
-			    xbps_get_pkg_dict_from_metadata_plist(pkgname,
-			    XBPS_PKGPROPS);
+			rv = extract_metafile(ar, entry, XBPS_PKGPROPS,
+			    pkgname, version, false, flags);
+			if (rv != 0)
+				goto out;
+
+			propsd = xbps_get_pkg_dict_from_metadata_plist(
+			    pkgname, XBPS_PKGPROPS);
 			if (propsd == NULL) {
 				rv = errno;
 				goto out;
 			}
-			/* Pass to next entry if successful */
 			entry_idx++;
+			RUN_PROGRESS_CB();
 			continue;
 		}
 		/*
@@ -266,12 +317,32 @@ unpack_archive_fini(prop_dictionary_t pkg_repod,
 			 * required metadata files weren't found, bail out.
 			 * This is not an XBPS binary package.
 			 */
-			if (entry_idx >= 3)
+			if (entry_idx >= 3) {
+				xbps_error_printf("invalid binary pkg archive"
+				    "for `%s-%s'\n", pkgname, version);
 				return ENODEV;
+			}
 
 			entry_idx++;
 			continue;
 		}
+		/*
+		 * Compute total entries in progress data, if set.
+		 * total_entries = files + conf_files + links.
+		 */
+		if (xpd != NULL && xpd->entry_total_count == -1) {
+			xpd->entry_total_count = 0;
+			array = prop_dictionary_get(filesd, "files");
+			xpd->entry_total_count +=
+			    (ssize_t)prop_array_count(array);
+			array = prop_dictionary_get(filesd, "conf_files");
+			xpd->entry_total_count +=
+			    (ssize_t)prop_array_count(array);
+			array = prop_dictionary_get(filesd, "links");
+			xpd->entry_total_count +=
+			    (ssize_t)prop_array_count(array);
+		}
+
 		/*
 		 * Handle configuration files. Check if current entry is
 		 * a configuration file and take action if required. Skip
@@ -283,6 +354,9 @@ unpack_archive_fini(prop_dictionary_t pkg_repod,
 			/* error */
 			goto out;
 		} else if (rv == 1) {
+			if (xpd != NULL)
+				xpd->entry_is_conf = true;
+
 			rv = xbps_entry_install_conf_file(filesd,
 			    entry, entry_pname, pkgname, version);
 			if (rv == -1) {
@@ -292,14 +366,15 @@ unpack_archive_fini(prop_dictionary_t pkg_repod,
 				/*
 				 * Configuration file should be installed.
 				 */
-				lflags &= ~ARCHIVE_EXTRACT_NO_OVERWRITE;
-				lflags &= ~ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER;
+				flags &= ~ARCHIVE_EXTRACT_NO_OVERWRITE;
+				flags &= ~ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER;
 			} else {
 				/*
 				 * Keep current configuration file
 				 * as is now and pass to next entry.
 				 */
 				archive_read_data_skip(ar);
+				RUN_PROGRESS_CB();
 				continue;
 			}
 		}
@@ -326,31 +401,32 @@ unpack_archive_fini(prop_dictionary_t pkg_repod,
 		    "replace-files-in-pkg-update",
 		    &replace_files_in_pkg_update);
 		if (replace_files_in_pkg_update) {
-			lflags &= ~ARCHIVE_EXTRACT_NO_OVERWRITE;
-			lflags &= ~ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER;
+			flags &= ~ARCHIVE_EXTRACT_NO_OVERWRITE;
+			flags &= ~ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER;
 		}
 
 		/*
 		 * Extract entry from archive.
 		 */
-		if (archive_read_extract(ar, entry, lflags) != 0) {
+		if (archive_read_extract(ar, entry, flags) != 0) {
 			rv = archive_errno(ar);
-			if (rv && rv != EEXIST) {
-				fprintf(stderr, "ERROR: extracting `%s' "
-				    "(%s) ...exiting!\n", entry_pname,
-				    archive_error_string(ar));
+			if (rv != EEXIST) {
+				xbps_error_printf("failed to extract `%s' "
+				    "from `%s-%s': %s\n", entry_pname,
+				    pkgname, version, strerror(rv));
 				goto out;
-			} else if (rv == EEXIST) {
-				if (flags & XBPS_FLAG_VERBOSE) {
-					fprintf(stderr,
-					    "WARNING: ignoring existent "
-					    "entry: %s\n", entry_pname);
-				}
+			} else {
+				xbps_dbg_printf("WARNING: ignoring existing "
+				    "entry: %s\n", entry_pname);
+				RUN_PROGRESS_CB();
 				continue;
 			}
 		}
-		if (flags & XBPS_FLAG_VERBOSE)
-			printf(" %s\n", entry_pname);
+
+		if (xpd != NULL)
+			xpd->entry_extract_count++;
+
+		RUN_PROGRESS_CB();
 	}
 
 	if ((rv = archive_errno(ar)) == 0) {
@@ -390,8 +466,11 @@ unpack_archive_fini(prop_dictionary_t pkg_repod,
 		 * is reachable.
 		 */
 		if (!prop_dictionary_externalize_to_zfile(filesd, buf)) {
-			free(buf);
 			rv = errno;
+			xbps_error_printf("failed to extract metadata %s file"
+			    "for `%s-%s': %s\n", XBPS_PKGFILES, pkgname,
+			    version, strerror(rv));
+			free(buf);
 			goto out;
 		}
 		free(buf);
@@ -405,14 +484,26 @@ out:
 
 	return rv;
 }
+#undef RUN_PROGRESS_CB
+
+void
+xbps_unpack_binary_pkg_set_progress_cb(void (*fn)(void *),
+				       struct xbps_progress_data *xpd)
+{
+	assert(fn != NULL);
+	assert(xpd != NULL);
+
+	unpack_progress_cb = fn;
+	sp_xpd = xpd;
+}
 
 int
 xbps_unpack_binary_pkg(prop_dictionary_t pkg_repod)
 {
+	struct archive *ar;
 	const char *pkgname, *version, *repoloc;
-	struct archive *ar = NULL;
-	char *binfile = NULL;
-	int pkg_fd, rv = 0;
+	char *bpkg;
+	int rv = 0;
 
 	assert(pkg_repod != NULL);
 
@@ -420,50 +511,60 @@ xbps_unpack_binary_pkg(prop_dictionary_t pkg_repod)
 	prop_dictionary_get_cstring_nocopy(pkg_repod, "version", &version);
 	prop_dictionary_get_cstring_nocopy(pkg_repod, "repository", &repoloc);
 
-	binfile = xbps_get_binpkg_repo_uri(pkg_repod, repoloc);
-	if (binfile == NULL)
-		return EINVAL;
-
-	if ((pkg_fd = open(binfile, O_RDONLY)) == -1) {
-		rv = errno;
-		xbps_dbg_printf("cannot open '%s' for unpacking %s\n",
-		    binfile, strerror(errno));
-		free(binfile);
-		goto out;
+	bpkg = xbps_get_binpkg_repo_uri(pkg_repod, repoloc);
+	if (bpkg == NULL) {
+		xbps_error_printf("cannot determine binary pkg file "
+		    "for `%s-%s': %s\n", pkgname, version, strerror(errno));
+		return errno;
 	}
-	free(binfile);
 
 	ar = archive_read_new();
 	if (ar == NULL) {
-		rv = errno;
+		rv = ENOMEM;
 		goto out;
 	}
-
 	/*
 	 * Enable support for tar format and all compression methods.
 	 */
 	archive_read_support_compression_all(ar);
 	archive_read_support_format_tar(ar);
 
-	if (archive_read_open_fd(ar, pkg_fd,
-	     ARCHIVE_READ_BLOCKSIZE) != 0) {
-		rv = errno;
+	if (archive_read_open_filename(ar, bpkg, ARCHIVE_READ_BLOCKSIZE) != 0) {
+		rv = archive_errno(ar);
+		xbps_error_printf("failed to open `%s' binpkg: %s\n",
+		    bpkg, strerror(rv));
 		goto out;
 	}
-
-	if ((rv = unpack_archive_fini(pkg_repod, ar, pkgname, version)) != 0)
+	/*
+	 * Set extract progress callback if specified.
+	 */
+	if (unpack_progress_cb != NULL && sp_xpd != NULL) {
+		sp_xpd->entry_extract_count = -1;
+		sp_xpd->entry_total_count = -1;
+	}
+	/*
+	 * Extract archive files.
+	 */
+	rv = unpack_archive(pkg_repod, ar, sp_xpd, pkgname, version);
+	if (rv != 0) {
+		xbps_error_printf("failed to unpack `%s' binpkg: %s\n",
+		    bpkg, strerror(rv));
 		goto out;
+	}
 
 	/*
 	 * Set package state to unpacked.
 	 */
 	rv = xbps_set_pkg_state_installed(pkgname, XBPS_PKG_STATE_UNPACKED);
-
+	if (rv != 0) {
+		xbps_error_printf("failed to set `%s-%s' to unpacked "
+		    "state: %s\n", pkgname, version, strerror(rv));
+	}
 out:
+	if (bpkg)
+		free(bpkg);
 	if (ar)
 		archive_read_finish(ar);
-	if (pkg_fd != -1)
-		(void)close(pkg_fd);
 
 	return rv;
 }