From 8c2ccea4357ec1cc1070ed822e708187917fe4ce Mon Sep 17 00:00:00 2001 From: Juan RP 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 #include #include +#include #include +#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 #include +#include +#include #include #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; }