diff --git a/bin/xbps-bin/main.c b/bin/xbps-bin/main.c index 9ea6431a848..51e82088502 100644 --- a/bin/xbps-bin/main.c +++ b/bin/xbps-bin/main.c @@ -57,11 +57,12 @@ usage(void) " -v\t\t\n" " Options used by the files target:\n" " -C\t\tTo check the SHA256 hash for any listed file.\n" - " Options used by the remove target:\n" + " Options used by the (auto)remove target:\n" " -f\t\tForce removal, even if package is required by other\n" " \t\tpackages that are currently installed.\n" "\n" " Examples:\n" + " $ xbps-bin autoremove\n" " $ xbps-bin install klibc\n" " $ xbps-bin -r /path/to/root install klibc\n" " $ xbps-bin -C files klibc\n" @@ -142,10 +143,14 @@ int main(int argc, char **argv) { prop_dictionary_t dict; - prop_array_t reqby; + prop_array_t reqby, orphans; + prop_object_t obj; + prop_object_iterator_t iter; + static size_t count; + const char *pkgname, *version; char *plist, *root = NULL; int c, flags = 0, rv = 0; - bool chkhash = false, forcerm = false; + bool chkhash = false, forcerm = false, verbose = false; while ((c = getopt(argc, argv, "Cfr:v")) != -1) { switch (c) { @@ -161,6 +166,7 @@ main(int argc, char **argv) xbps_set_rootdir(root); break; case 'v': + verbose = true; flags |= XBPS_UNPACK_VERBOSE; break; case '?': @@ -234,34 +240,48 @@ main(int argc, char **argv) printf("Package %s is not installed.\n", argv[1]); exit(EXIT_FAILURE); } + prop_dictionary_get_cstring_nocopy(dict, "version", &version); reqby = prop_dictionary_get(dict, "requiredby"); if (reqby != NULL && prop_array_count(reqby) > 0) { - printf("WARNING! %s is required by the following " - "packages:\n", argv[1]); + printf("WARNING! %s-%s is required by the following " + "packages:\n", argv[1], version); (void)xbps_callback_array_iter_in_dict(dict, "requiredby", show_reqby_pkgs, NULL); - prop_object_release(dict); if (!forcerm) { + prop_object_release(dict); printf("\n\nIf you are sure about this, use " "-f to force deletion for this package.\n"); exit(EXIT_FAILURE); } else - printf("\n\nForcing %s for deletion!\n", - argv[1]); + printf("\n\nForcing %s-%s for deletion!\n", + argv[1], version); } + printf("Removing package %s-%s ... ", argv[1], version); + if (verbose) + printf("\n"); + + (void)fflush(stdout); + rv = xbps_remove_binary_pkg(argv[1], root, flags); if (rv != 0) { - if (errno == ENOENT) - printf("Package %s is not installed.\n", - argv[1]); + if (!verbose) + printf("failed! (%s)\n", strerror(rv)); else - printf("Unable to remove %s (%s).\n", - argv[1], strerror(errno)); + printf("Unable to remove %s-%s (%s).\n", + argv[1], version, strerror(errno)); + + prop_object_release(dict); exit(EXIT_FAILURE); } - printf("Package %s removed successfully.\n", argv[1]); + if (!verbose) + printf("done.\n"); + else + printf("Package %s-%s removed successfully.\n", + argv[1], version); + + prop_object_release(dict); } else if (strcasecmp(argv[0], "show") == 0) { /* Shows info about an installed binary package. */ @@ -287,20 +307,76 @@ main(int argc, char **argv) } else if (strcasecmp(argv[0], "autoremove") == 0) { /* - * Removes orphaned pkgs. These packages were installed + * Removes orphan pkgs. These packages were installed * as dependency and any installed package does not depend - * on it. + * on it currently. */ if (argc != 1) usage(); -#if 0 - rv = xbps_auto_remove_packages(); - if (rv != 0) { - printf("There was an error! (%s)\n", strerror(rv)); + orphans = xbps_find_orphan_packages(); + if (orphans == NULL) exit(EXIT_FAILURE); + if (orphans != NULL && prop_array_count(orphans) == 0) { + printf("There are not orphaned packages currently.\n"); + exit(EXIT_SUCCESS); } -#endif + + iter = prop_array_iterator(orphans); + if (iter == NULL) + exit(EXIT_FAILURE); + + printf("The following packages were installed automatically\n" + "(as dependencies) and aren't needed anymore:\n"); + while ((obj = prop_object_iterator_next(iter)) != NULL) { + prop_dictionary_get_cstring_nocopy(obj, "pkgname", + &pkgname); + prop_dictionary_get_cstring_nocopy(obj, "version", + &version); + if (count == 0) + printf("\n\t"); + else if (count == 4) { + printf("\n\t"); + count = 0; + } + printf("%s-%s ", pkgname, version); + count++; + } + printf("\n\n"); + if (!forcerm) { + printf("If you are really sure you don't need them, " + "use -f to confirm.\n"); + goto out; + } + + prop_object_iterator_reset(iter); + + while ((obj = prop_object_iterator_next(iter)) != NULL) { + prop_dictionary_get_cstring_nocopy(obj, "pkgname", + &pkgname); + prop_dictionary_get_cstring_nocopy(obj, "version", + &version); + printf("Removing package %s-%s ... ", + pkgname, version); + if (verbose) + printf("\n"); + + (void)fflush(stdout); + + rv = xbps_remove_binary_pkg(pkgname, root, flags); + if (rv != 0) { + if (!verbose) + printf("failed! (%s)\n", strerror(rv)); + prop_object_iterator_release(iter); + prop_object_release(orphans); + exit(EXIT_FAILURE); + } + if (!verbose) + printf("done.\n"); + } +out: + prop_object_iterator_release(iter); + prop_object_release(orphans); } else { usage(); diff --git a/doc/TODO b/doc/TODO index fcaae6b2b09..fc84d1f0b30 100644 --- a/doc/TODO +++ b/doc/TODO @@ -15,8 +15,6 @@ Packages: xbps-bin: * Add support to handle conf_files and keep_dirs from package metadata. [PARTIALLY IMPLEMENTED] - * Add support to detect orphaned packages, something like - "apt-get autoremove" [IN PROGRESS] * Add support to install binary packages without any repository. * Check SHA256 hash of pkg and dependencies before installing. Currently the hash is checked before a pkg is unpacked, this is diff --git a/include/util.h b/include/util.h index 3905a8307ab..91615bdf2a4 100644 --- a/include/util.h +++ b/include/util.h @@ -26,7 +26,7 @@ #ifndef _XBPS_UTIL_H_ #define _XBPS_UTIL_H_ -/* from lib/util.c */ +/* From lib/util.c */ char * xbps_append_full_path(bool, const char *, const char *); int xbps_check_file_hash(const char *, const char *); int xbps_check_is_installed_pkg(const char *); @@ -37,4 +37,7 @@ const char * xbps_get_pkg_version(const char *); bool xbps_pkg_has_rundeps(prop_dictionary_t); void xbps_set_rootdir(const char *); +/* From lib/orphans.c */ +prop_array_t xbps_find_orphan_packages(void); + #endif /* !_XBPS_UTIL_H_ */ diff --git a/lib/Makefile b/lib/Makefile index 0ec5272a78a..f0e581c3086 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -9,8 +9,8 @@ LIBXBPS = libxbps.so LIBXBPS_LDFLAGS = -larchive -lprop -shared -Wl,-soname,$(LIBXBPS).$(MAJOR) OBJECTS = cmpver.o depends.o fexec.o humanize_number.o install.o -OBJECTS += plist.o remove.o repository.o requiredby.o sha256.o -OBJECTS += sortdeps.o unpack.o util.o +OBJECTS += orphans.o plist.o remove.o repository.o requiredby.o +OBJECTS += sha256.o sortdeps.o unpack.o util.o all: $(LIBXBPS) .PHONY: all diff --git a/lib/orphans.c b/lib/orphans.c new file mode 100644 index 00000000000..a95cc64cec8 --- /dev/null +++ b/lib/orphans.c @@ -0,0 +1,171 @@ +/*- + * Copyright (c) 2009 Juan Romero Pardines. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#include + +struct orphan { + prop_array_t array; + const char *pkgname; + const char *version; +}; + +static int +find_orphan_pkg(prop_object_t obj, void *arg, bool *loop_done) +{ + struct orphan *orphan = arg; + prop_array_t reqby; + bool automatic = false; + + (void)loop_done; + + reqby = prop_dictionary_get(obj, "requiredby"); + if (reqby != NULL && prop_array_count(reqby) > 0) + return 0; + + if (!prop_dictionary_get_bool(obj, "automatic-install", &automatic)) + return EINVAL; + + if (automatic) + if (!prop_array_add(orphan->array, obj)) + return EINVAL; + + return 0; +} + +static int +find_indirect_orphan_pkg(prop_object_t obj, void *arg, bool *loop_done) +{ + struct orphan *orphan = arg; + prop_array_t reqby; + size_t len = 0; + char *pkg; + bool automatic = false; + + (void)loop_done; + + if (!prop_dictionary_get_bool(obj, "automatic-install", &automatic)) + return EINVAL; + + if (!automatic) + return 0; + + reqby = prop_dictionary_get(obj, "requiredby"); + if (reqby == NULL || prop_array_count(reqby) != 1) + return 0; + + len = strlen(orphan->pkgname) + strlen(orphan->version) + 2; + pkg = malloc(len); + if (pkg == NULL) + return ENOMEM; + + (void)snprintf(pkg, len, "%s-%s", orphan->pkgname, orphan->version); + + if (xbps_find_string_in_array(reqby, pkg)) { + if (!prop_array_add(orphan->array, obj)) { + free(pkg); + return EINVAL; + } + } + free(pkg); + + return 0; +} + +prop_array_t +xbps_find_orphan_packages(void) +{ + prop_dictionary_t dict; + prop_object_t obj; + prop_object_iterator_t iter; + struct orphan orphan; + char *plist; + int rv = 0; + + plist = xbps_append_full_path(true, NULL, XBPS_REGPKGDB); + if (plist == NULL) + return NULL; + + dict = prop_dictionary_internalize_from_file(plist); + if (dict == NULL) { + free(plist); + return NULL; + } + free(plist); + + orphan.array = prop_array_create(); + if (orphan.array == NULL) { + prop_object_release(dict); + return NULL; + } + + /* + * First look for direct orphan packages, i.e the ones + * that were required directly by a previous removed package. + */ + rv = xbps_callback_array_iter_in_dict(dict, "packages", + find_orphan_pkg, (void *)&orphan); + if (rv != 0) { + prop_object_release(dict); + prop_object_release(orphan.array); + return NULL; + } + + /* + * Now look if any of these packages have dependencies that + * were installed indirectly by some removed package. + */ + iter = prop_array_iterator(orphan.array); + if (iter == NULL) { + prop_object_release(dict); + prop_object_release(orphan.array); + return NULL; + } + + while ((obj = prop_object_iterator_next(iter)) != NULL) { + prop_dictionary_get_cstring_nocopy(obj, "pkgname", + &orphan.pkgname); + prop_dictionary_get_cstring_nocopy(obj, "version", + &orphan.version); + rv = xbps_callback_array_iter_in_dict(dict, "packages", + find_indirect_orphan_pkg, (void *)&orphan); + if (rv != 0) { + prop_object_iterator_release(iter); + prop_object_release(dict); + prop_object_release(orphan.array); + return NULL; + } + } + prop_object_iterator_release(iter); + prop_object_release(dict); + + return prop_array_copy(orphan.array); +}