/* $Id: autoremove.c,v 1.24 2009/05/08 10:03:03 imil Exp $ */

/*
 * Copyright (c) 2009 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Emile "iMil" Heitor <imil@NetBSD.org> .
 *
 * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
 *
 */

/* autoremove.c: cleanup orphan dependencies */

#include "pkg_dry.h"

#define AUTOREMOVE_WARNING "\
in order to remove packages from the autoremove list, flag those with the -k modifier."

static int 			removenb = 0;
static Plisthead	*keeplisthead;

static int
deptreecount(char *fullpkg, const char *query)
{
	Deptreehead	deptreehead;
	Pkgdeptree	*pdp;
	Pkglist		*pkglist;
	char		*pkgname;
	int			count = 0;

	/* first check the package itself is not keep-able */
	SLIST_FOREACH(pkglist, keeplisthead, next)
		if (strncmp(fullpkg, pkglist->pkgname, strlen(fullpkg)) == 0)
			return 1;

	SLIST_INIT(&deptreehead);

	XSTRDUP(pkgname, fullpkg);
	trunc_str(pkgname, '-', STR_BACKWARD);

	full_dep_tree(pkgname, query, &deptreehead, __func__, 0);

	SLIST_FOREACH(pdp, &deptreehead, next)
		if (pdp->pkgkeep)
			count++;

	free_deptree(&deptreehead);
	XFREE(pkgname);

	return count;
}

/* 
 * recursively find unneeded dependencies over 1st autoremove line
 */
void
find_useless(Deptreehead *removehead)
{
	Deptreehead	remtreehead;
	Pkgdeptree	*rdp, *premove, *premadd, *testdp;
	Plisthead	*plisthead;
	Pkglist		*mapplist;
	char		*pkgname;
	int			keepcount, exists;

	if ((plisthead = rec_pkglist(LOCAL_PKGS_QUERY)) == NULL)
		return; /* empty local pkg list, should not happen */

	/* record all dependency tree for 1st autoremove level */
	SLIST_FOREACH(premove, removehead, next) {
		XSTRDUP(pkgname, premove->depname);
		trunc_str(pkgname, '-', STR_BACKWARD);

		SLIST_INIT(&remtreehead);

		/* returns dependencies */
		full_dep_tree(pkgname, LOCAL_DIRECT_DEPS, &remtreehead, __func__, 0);

		/* browse full dependencies for autoremove list */
		SLIST_FOREACH(rdp, &remtreehead, next) {
			if ((mapplist = map_pkg_to_dep(plisthead, rdp->depname)) == NULL)
				continue; /* database corruption, should not happen */
			XSTRDUP(pkgname, mapplist->pkgname);
			if (pkgname == NULL)
				/* database corruption, empty mapping */
				continue;
			/* how many reverse dependencies keep flag */
			keepcount = deptreecount(pkgname, LOCAL_REVERSE_DEPS);
			/* if there are keep packages, this dependency
			 * is needed by another non-autoremovable package
			 */
			if (keepcount == 0) {

				exists = 0;

				/* check if package is already recorded as to-remove */
				SLIST_FOREACH(testdp, removehead, next)	{
					if (testdp->depname == NULL || pkgname == NULL)
						/* database corruption */
						continue;
					if (strncmp(testdp->depname, pkgname,
							strlen(testdp->depname)) == 0)
						exists = 1;
				}

				if (exists) {
					XFREE(pkgname);
					continue;
				}

				XMALLOC(premadd, sizeof(Pkgdeptree));
				XSTRDUP(premadd->depname, pkgname);
				premadd->matchname = NULL; /* safety */
				premadd->level = 0;
				
				SLIST_INSERT_HEAD(removehead, premadd, next);

				removenb++;

				XFREE(pkgname);
			} /* keepcount == 0, no keep packages depends on this package */
		} /* premove SLIST_FOREACH */

		XFREE(pkgname);
		free_deptree(&remtreehead);
	}

	free_pkglist(plisthead);
	XFREE(plisthead);
}

void
pkg_dry_autoremove()
{
	Plisthead	*plisthead;
	Pkglist		*pkglist;
	Deptreehead	deptreehead, removehead, *orderedhead;
	Pkgdeptree	*premove;
	char		*pkgname, *p, *toremove = NULL;

	/* test if there's any keep package */
	if ((plisthead = rec_pkglist(KEEP_LOCAL_PKGS)) == NULL)
		errx(EXIT_FAILURE, "no packages have been installed with %s",
			getprogname());

	free_pkglist(plisthead);
	XFREE(plisthead);

	if ((plisthead = rec_pkglist(NOKEEP_LOCAL_PKGS)) == NULL)
		return;

	SLIST_INIT(&removehead);

	/* browse local packages not marked as "keep" */
	SLIST_FOREACH(pkglist, plisthead, next) {
		XSTRDUP(pkgname, pkglist->pkgname);

		if ((p = strrchr(pkgname, '-')) == NULL)
			return; /* should not happen */
		*p = '\0';

		SLIST_INIT(&deptreehead);

		full_dep_tree(pkgname, LOCAL_REVERSE_DEPS, &deptreehead, __func__, 0);

		if (SLIST_EMPTY(&deptreehead)) { /* empty dep list */
			XMALLOC(premove, sizeof(Pkgdeptree));
			XSTRDUP(premove->depname, pkglist->pkgname);
			premove->matchname = NULL; /* safety */
			premove->level = 0;

			SLIST_INSERT_HEAD(&removehead, premove, next);

			removenb++;
		}

		free_deptree(&deptreehead);
	}

	free_pkglist(plisthead);
	XFREE(plisthead);

	if ((keeplisthead = rec_pkglist(KEEP_LOCAL_PKGS)) == NULL)
		return;

	find_useless(&removehead);

	free_pkglist(keeplisthead);
	XFREE(keeplisthead);

	orderedhead = order_remove(&removehead);

	if (!SLIST_EMPTY(orderedhead)) {
		SLIST_FOREACH(premove, orderedhead, next)
			toremove = action_list(toremove, premove->depname);

		/* we want this action to be confirmed */
		yesflag = 0;

		printf("%s\n", AUTOREMOVE_WARNING);
		printf("%d packages to be autoremoved: %s\n", removenb, toremove);
		if (check_yesno()) {
			SLIST_FOREACH(premove, orderedhead, next) {
				printf("removing %s...\n", premove->depname);
#ifdef DEBUG
				printf("%s -f %s\n", PKG_DELETE, premove->depname);
#else		
				fexec(PKG_DELETE, "-f", premove->depname, NULL);
#endif
			}
			update_db(LOCAL_SUMMARY, NULL);
		}
	}

	XFREE(toremove);
	free_deptree(orderedhead);
	XFREE(orderedhead);
}

void
show_pkg_keep(void)
{
	Plisthead	*plisthead;
	Pkglist		*pkglist;

	plisthead = rec_pkglist(KEEP_LOCAL_PKGS);

	if (plisthead == NULL) {
		printf("keep-list is empty.\n");
		return;
	}

	SLIST_FOREACH(pkglist, plisthead, next)
		printf("%s is marked as non-autoremovable\n", pkglist->pkgname);

	free_pkglist(plisthead);
	XFREE(plisthead);
}

static char *
match_keep(char *pkgkeep, char *pkgname, char *real_pkg)
{
	int pkglen;

	pkglen = strlen(pkgname);

	if (strlen(pkgkeep) == pkglen &&
		strncmp(pkgname, pkgkeep, pkglen) == 0) {
		XFREE(pkgname);
		XSTRDUP(pkgname, real_pkg);

		return pkgname;
	}

	return NULL;
}

/* flag packages in pkgargs as non or autoremovable */
void
pkg_keep(int type, char **pkgargs)
{
	Plisthead			*plisthead;
	Pkglist				*pkglist;
	char				**pkeep, *pkgname, query[BUFSIZ];

	plisthead = rec_pkglist(LOCAL_PKGS_QUERY);

	if (plisthead == NULL) /* no packages recorded */
		return;

	/* parse packages by their command line names */
	for (pkeep = pkgargs; *pkeep != NULL; pkeep++) {
		pkgname = NULL;
		/* find real package name */
		SLIST_FOREACH(pkglist, plisthead, next) {
			/* PKGNAME match */
			if (exact_pkgfmt(*pkeep)) /* argument was a full package name */
				trunc_str(*pkeep, '-', STR_BACKWARD);

			XSTRDUP(pkgname, pkglist->pkgname);
			trunc_str(pkgname, '-', STR_BACKWARD);

			if ((pkgname = 
					match_keep(*pkeep, pkgname, pkglist->pkgname)) != NULL)
				break;

			XFREE(pkgname);
		} /* SLIST pkglist */

		if (pkgname != NULL) {
			switch (type) {
			case KEEP:
				printf("marking %s as non auto-removeable\n", pkgname);
				snprintf(query, BUFSIZ, KEEP_PKG, pkgname);
				break;
			case UNKEEP:
				printf("marking %s as auto-removeable\n", pkgname);
				snprintf(query, BUFSIZ, UNKEEP_PKG, pkgname);
				break;
			}

			drydb_doquery(query, NULL, NULL);
			XFREE(pkgname);
		} else
			printf("no such package %s installed\n", *pkeep);
	} /* for (pkeep) */

	free_pkglist(plisthead);
	XFREE(plisthead);
}

