933 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			933 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*-
 | 
						|
 * Copyright (c) 2009 Joerg Sonnenberger <joerg@NetBSD.org>.
 | 
						|
 * Copyright (c) 2003 Johnny Lam <jlam@NetBSD.org>.
 | 
						|
 * 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 COPYRIGHT HOLDERS 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
 | 
						|
 * COPYRIGHT HOLDERS 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.
 | 
						|
 */
 | 
						|
#if HAVE_CONFIG_H
 | 
						|
#include "config.h"
 | 
						|
#endif
 | 
						|
#ifndef __minix
 | 
						|
#include <nbcompat.h>
 | 
						|
#endif
 | 
						|
#if HAVE_SYS_CDEFS_H
 | 
						|
#include <sys/cdefs.h>
 | 
						|
#endif
 | 
						|
#ifndef __minix
 | 
						|
__RCSID("$NetBSD: pkg_delete.c,v 1.11 2010/02/03 14:20:14 joerg Exp $");
 | 
						|
#endif
 | 
						|
 | 
						|
#if HAVE_ERR_H
 | 
						|
#include <err.h>
 | 
						|
#endif
 | 
						|
#include <stdio.h>
 | 
						|
#include <stdlib.h>
 | 
						|
 | 
						|
#include "lib.h"
 | 
						|
 | 
						|
static const char *pkgdb;
 | 
						|
static const char *destdir;
 | 
						|
static const char *prefix;
 | 
						|
 | 
						|
static int keep_preserve;
 | 
						|
static int no_deinstall;
 | 
						|
static int find_by_filename;
 | 
						|
static int unregister_only;
 | 
						|
static int pkgdb_update_only;
 | 
						|
static int delete_recursive;
 | 
						|
static int delete_new_leaves;
 | 
						|
static int delete_automatic_leaves;
 | 
						|
 | 
						|
static void
 | 
						|
usage(void)
 | 
						|
{
 | 
						|
	fprintf(stderr, "usage: pkg_delete [-DFfkNnORrVv] [-K pkg_dbdir]"
 | 
						|
	    " [-P destdir] [-p prefix] pkg-name ...\n");
 | 
						|
	exit(1);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
add_by_filename(lpkg_head_t *pkgs, const char *filename)
 | 
						|
{
 | 
						|
	lpkg_t *lpp;
 | 
						|
	char *s;
 | 
						|
 | 
						|
	if ((s = pkgdb_retrieve(filename)) == NULL) {
 | 
						|
		warnx("No matching package for file `%s' in pkgdb", filename);
 | 
						|
		return 1;
 | 
						|
	}
 | 
						|
 | 
						|
	/* XXX Verify that pkgdb is consistent? Trust it for now... */
 | 
						|
 | 
						|
	lpp = alloc_lpkg(s);
 | 
						|
	TAILQ_INSERT_TAIL(pkgs, lpp, lp_link);
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
add_by_pattern(lpkg_head_t *pkgs, const char *pattern)
 | 
						|
{
 | 
						|
	switch (add_installed_pkgs_by_pattern(pattern, pkgs)) {
 | 
						|
	case 0:
 | 
						|
		warnx("No package matching `%s' found", pattern);
 | 
						|
		return 1;
 | 
						|
	case -1:
 | 
						|
		warnx("Error while iterating package database for `%s'",
 | 
						|
		    pattern);
 | 
						|
		return 1;
 | 
						|
	default:
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * The argument is either a fixed package name or an absolute path.
 | 
						|
 * The latter is recognized for legacy compatibility and must point
 | 
						|
 * into the package database.
 | 
						|
 */
 | 
						|
static int
 | 
						|
add_by_pkgname(lpkg_head_t *pkgs, char *pkg)
 | 
						|
{
 | 
						|
	char *s;
 | 
						|
	lpkg_t *lpp;
 | 
						|
	size_t l;
 | 
						|
	const char *orig_pkg = pkg;
 | 
						|
 | 
						|
	if (pkg[0] == '/') {
 | 
						|
		l = strlen(pkgdb);
 | 
						|
		if (strncmp(pkg, pkgdb, l) || pkg[l] != '/') {
 | 
						|
			warnx("Absolute path is not relative to "
 | 
						|
			    "package database, skipping: %s", pkg);
 | 
						|
			return 1;
 | 
						|
		}
 | 
						|
		pkg += l + 1;
 | 
						|
	}
 | 
						|
	l = strcspn(pkg, "/");
 | 
						|
	if (pkg[l + strspn(pkg + l, "/")] != '\0') {
 | 
						|
		warnx("`%s' is not a package name, skipping", orig_pkg);
 | 
						|
		return 1;
 | 
						|
	}
 | 
						|
	pkg[l] = '\0';
 | 
						|
 | 
						|
	s = pkgdb_pkg_file(pkg, CONTENTS_FNAME);
 | 
						|
	if (fexists(s)) {
 | 
						|
		free(s);
 | 
						|
		lpp = alloc_lpkg(pkg);
 | 
						|
		TAILQ_INSERT_TAIL(pkgs, lpp, lp_link);
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
	free(s);
 | 
						|
 | 
						|
	switch (add_installed_pkgs_by_basename(pkg, pkgs)) {
 | 
						|
	case 0:
 | 
						|
		warnx("No matching package for basename `%s' of `%s'",
 | 
						|
		    pkg, orig_pkg);
 | 
						|
		return 1;
 | 
						|
	case -1:
 | 
						|
		warnx("Error expanding basename `%s' of `%s'",
 | 
						|
		    pkg, orig_pkg);
 | 
						|
		return 1;
 | 
						|
	default:
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Evaluate +REQUIRED_BY.  This function is used for four different
 | 
						|
 * tasks:
 | 
						|
 * 0: check if no depending packages remain
 | 
						|
 * 1: like 0, but prepend the depending packages to pkgs if they exist
 | 
						|
 * 2: print remaining packages to stderr
 | 
						|
 * 3: check all and at least one depending packages have been removed
 | 
						|
 */
 | 
						|
static int
 | 
						|
process_required_by(const char *pkg, lpkg_head_t *pkgs,
 | 
						|
    lpkg_head_t *sorted_pkgs, int action)
 | 
						|
{
 | 
						|
	char line[MaxPathSize], *eol, *fname;
 | 
						|
	FILE *fp;
 | 
						|
	lpkg_t *lpp;
 | 
						|
	int got_match, got_miss;
 | 
						|
 | 
						|
	fname = pkgdb_pkg_file(pkg, REQUIRED_BY_FNAME);
 | 
						|
	if (!fexists(fname)) {
 | 
						|
		free(fname);
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
 | 
						|
	if ((fp = fopen(fname, "r")) == NULL) {
 | 
						|
		warn("Failed to open `%s'", fname);
 | 
						|
		free(fname);
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
	free(fname);
 | 
						|
 | 
						|
	got_match = 0;
 | 
						|
	got_miss = 0;
 | 
						|
 | 
						|
	while (fgets(line, sizeof(line), fp)) {
 | 
						|
		if ((eol = strrchr(line, '\n')) != NULL)
 | 
						|
			*eol = '\0';
 | 
						|
		TAILQ_FOREACH(lpp, sorted_pkgs, lp_link) {
 | 
						|
			if (strcmp(lpp->lp_name, line) == 0)
 | 
						|
				break;
 | 
						|
		}
 | 
						|
		if (lpp != NULL) {
 | 
						|
			got_match = 1;
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
		got_miss = 1;
 | 
						|
		if (pkgs) {
 | 
						|
			TAILQ_FOREACH(lpp, pkgs, lp_link) {
 | 
						|
				if (strcmp(lpp->lp_name, line) == 0)
 | 
						|
					break;
 | 
						|
			}
 | 
						|
			if (lpp != NULL)
 | 
						|
				continue;
 | 
						|
		}
 | 
						|
		switch (action) {
 | 
						|
		case 0:
 | 
						|
			fclose(fp);
 | 
						|
			return 1;
 | 
						|
		case 1:
 | 
						|
			lpp = alloc_lpkg(line);
 | 
						|
			TAILQ_INSERT_HEAD(pkgs, lpp, lp_link);
 | 
						|
			break;
 | 
						|
		case 2:
 | 
						|
			fprintf(stderr, "\t%s\n", line);
 | 
						|
			break;
 | 
						|
		case 3:
 | 
						|
			fclose(fp);
 | 
						|
			return 0;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	fclose(fp);
 | 
						|
 | 
						|
	return (action == 3 ? got_match : got_miss);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Main function to order the patterns from the command line and
 | 
						|
 * add the subtrees for -r processing as needed.
 | 
						|
 *
 | 
						|
 * The first part ensures that all packages are listed at most once
 | 
						|
 * in pkgs. Afterwards the list is scanned for packages without depending
 | 
						|
 * packages. Each such package is moved to sorted_pkgs in order.
 | 
						|
 * If -r is given, all dependencies are inserted at the head of pkgs.
 | 
						|
 * The loop has to continue as long as progress is made. This can happen
 | 
						|
 * either because another package has been added to pkgs due to recursion
 | 
						|
 * (head of pkgs changed) or because a package has no more depending packages
 | 
						|
 * (tail of sorted_pkgs changed).
 | 
						|
 *
 | 
						|
 * If no progress is made, the remaining packages are moved to sorted_pkgs
 | 
						|
 * and an error is returned for the !Force case.
 | 
						|
 */
 | 
						|
static int
 | 
						|
sort_and_recurse(lpkg_head_t *pkgs, lpkg_head_t *sorted_pkgs)
 | 
						|
{
 | 
						|
	lpkg_t *lpp, *lpp2, *lpp_next, *lpp_old_tail, *lpp_old_head;
 | 
						|
	int rv;
 | 
						|
 | 
						|
	TAILQ_FOREACH_SAFE(lpp, pkgs, lp_link, lpp_next) {
 | 
						|
		TAILQ_FOREACH(lpp2, pkgs, lp_link) {
 | 
						|
			if (lpp != lpp2 &&
 | 
						|
			    strcmp(lpp->lp_name, lpp2->lp_name) == 0)
 | 
						|
				break;
 | 
						|
		}
 | 
						|
		if (lpp2 == NULL)
 | 
						|
			continue;
 | 
						|
		TAILQ_REMOVE(pkgs, lpp, lp_link);
 | 
						|
		free_lpkg(lpp);
 | 
						|
	}
 | 
						|
 | 
						|
	while (!TAILQ_EMPTY(pkgs)) {
 | 
						|
		lpp_old_tail = TAILQ_LAST(sorted_pkgs, _lpkg_head_t);
 | 
						|
		lpp_old_head = TAILQ_FIRST(pkgs);
 | 
						|
 | 
						|
		TAILQ_FOREACH_SAFE(lpp, pkgs, lp_link, lpp_next) {
 | 
						|
			rv = process_required_by(lpp->lp_name, pkgs,
 | 
						|
			    sorted_pkgs, delete_recursive ? 1 : 0);
 | 
						|
			if (rv)
 | 
						|
				continue;
 | 
						|
			TAILQ_REMOVE(pkgs, lpp, lp_link);
 | 
						|
			TAILQ_INSERT_TAIL(sorted_pkgs, lpp, lp_link);
 | 
						|
		}
 | 
						|
 | 
						|
		if (lpp_old_tail == TAILQ_LAST(sorted_pkgs, _lpkg_head_t) &&
 | 
						|
		    lpp_old_head == TAILQ_FIRST(pkgs))
 | 
						|
			break;
 | 
						|
	}
 | 
						|
 | 
						|
	if (TAILQ_EMPTY(pkgs))
 | 
						|
		return 0;
 | 
						|
 | 
						|
	while (!TAILQ_EMPTY(pkgs)) {
 | 
						|
		lpp = TAILQ_FIRST(pkgs);
 | 
						|
		TAILQ_REMOVE(pkgs, lpp, lp_link);
 | 
						|
		fprintf(stderr,
 | 
						|
		    "Package `%s' is still required by other packages:\n",
 | 
						|
		    lpp->lp_name);		
 | 
						|
		process_required_by(lpp->lp_name, NULL, sorted_pkgs, 2);
 | 
						|
		if (Force) {
 | 
						|
			TAILQ_INSERT_TAIL(sorted_pkgs, lpp, lp_link);
 | 
						|
		} else
 | 
						|
			free_lpkg(lpp);
 | 
						|
	}
 | 
						|
 | 
						|
	return !Force;
 | 
						|
}
 | 
						|
 | 
						|
struct find_leaves_data {
 | 
						|
	lpkg_head_t *pkgs;
 | 
						|
	int progress;
 | 
						|
};
 | 
						|
 | 
						|
/*
 | 
						|
 * Iterator for finding leaf packages.
 | 
						|
 * Packages that are marked as not for deletion are not considered as
 | 
						|
 * leaves.  For all other packages it is checked if at least one package
 | 
						|
 * that depended on them is to be removed AND no depending package remains.
 | 
						|
 * If that is the case, the package is appened to the sorted list.
 | 
						|
 * As this package can't have depending packages left, the topological order
 | 
						|
 * remains consistent.
 | 
						|
 */
 | 
						|
static int
 | 
						|
find_new_leaves_iter(const char *pkg, void *cookie)
 | 
						|
{
 | 
						|
	char *fname;
 | 
						|
	struct find_leaves_data *data = cookie;
 | 
						|
	lpkg_t *lpp;
 | 
						|
 | 
						|
	fname = pkgdb_pkg_file(pkg, PRESERVE_FNAME);
 | 
						|
	if (fexists(fname)) {
 | 
						|
		free(fname);
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
	free(fname);
 | 
						|
 | 
						|
	if (delete_automatic_leaves && !delete_new_leaves &&
 | 
						|
	    !is_automatic_installed(pkg))
 | 
						|
		return 0;
 | 
						|
 | 
						|
	/* Check whether this package is already on the list first. */
 | 
						|
	TAILQ_FOREACH(lpp, data->pkgs, lp_link) {
 | 
						|
		if (strcmp(lpp->lp_name, pkg) == 0)
 | 
						|
			return 0;
 | 
						|
	}
 | 
						|
 | 
						|
	if (process_required_by(pkg, NULL, data->pkgs, 3) == 1) {
 | 
						|
		lpp = alloc_lpkg(pkg);
 | 
						|
		TAILQ_INSERT_TAIL(data->pkgs, lpp, lp_link);
 | 
						|
		data->progress = 0;
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Iterate over all installed packages and look for new leaf packages.
 | 
						|
 * As long as the loop adds one new leaf package, processing continues.
 | 
						|
 */
 | 
						|
static void
 | 
						|
find_new_leaves(lpkg_head_t *pkgs)
 | 
						|
{
 | 
						|
	struct find_leaves_data data;
 | 
						|
 | 
						|
	data.pkgs = pkgs;
 | 
						|
	do {
 | 
						|
		data.progress = 0;
 | 
						|
		iterate_pkg_db(find_new_leaves_iter, &data);
 | 
						|
	} while (data.progress);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Check that no entry on the package list is marked as not for deletion.
 | 
						|
 */
 | 
						|
static int
 | 
						|
find_preserve_pkgs(lpkg_head_t *pkgs)
 | 
						|
{
 | 
						|
	lpkg_t *lpp, *lpp_next;
 | 
						|
	char *fname;
 | 
						|
	int found_preserve;
 | 
						|
 | 
						|
	found_preserve = 0;
 | 
						|
	TAILQ_FOREACH_SAFE(lpp, pkgs, lp_link, lpp_next) {
 | 
						|
		fname = pkgdb_pkg_file(lpp->lp_name, PRESERVE_FNAME);
 | 
						|
		if (!fexists(fname)) {
 | 
						|
			free(fname);
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
		free(fname);
 | 
						|
		if (keep_preserve) {
 | 
						|
			TAILQ_REMOVE(pkgs, lpp, lp_link);
 | 
						|
			free_lpkg(lpp);
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
		if (!found_preserve)
 | 
						|
			warnx("The following packages are marked as not "
 | 
						|
			    "for deletion:");
 | 
						|
		found_preserve = 1;
 | 
						|
		fprintf(stderr, "\t%s\n", lpp->lp_name);
 | 
						|
	}
 | 
						|
	if (!found_preserve)
 | 
						|
		return 0;
 | 
						|
	if (Force == 0 || (!unregister_only && Force == 1))
 | 
						|
		return 1;
 | 
						|
	fprintf(stderr, "...but will delete them anyway\n");
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Remove package from view.  This is calling pkg_deinstall again.
 | 
						|
 */
 | 
						|
static int
 | 
						|
remove_pkg_from_view(const char *pkg)
 | 
						|
{
 | 
						|
	char line[MaxPathSize], *fname, *eol;
 | 
						|
	FILE *fp;
 | 
						|
 | 
						|
	fname = pkgdb_pkg_file(pkg, VIEWS_FNAME);
 | 
						|
	if (isemptyfile(fname)) {
 | 
						|
		free(fname);
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
	if ((fp = fopen(fname, "r")) == NULL) {
 | 
						|
		warn("Unable to open `%s', aborting", fname);
 | 
						|
		free(fname);
 | 
						|
		return 1;
 | 
						|
	}
 | 
						|
	free(fname);
 | 
						|
	while (fgets(line, sizeof(line), fp) != NULL) {
 | 
						|
		if ((eol = strrchr(line, '\n')) != NULL)
 | 
						|
			*eol = '\0';
 | 
						|
		if (Verbose || Fake)
 | 
						|
			printf("Deleting package `%s' instance from `%s' view\n",
 | 
						|
			    pkg, line);
 | 
						|
		if (Fake)
 | 
						|
			continue;
 | 
						|
		if (fexec_skipempty(BINDIR "/pkg_delete", "-K", line,
 | 
						|
				    Fake ? "-n" : "",
 | 
						|
				    (Force > 1) ? "-f" : "",
 | 
						|
				    (Force > 0) ? "-f" : "",
 | 
						|
				    pkg, NULL) != 0) {
 | 
						|
			warnx("Unable to delete package `%s' from view `%s'",
 | 
						|
			    pkg, line);
 | 
						|
			fclose(fp);
 | 
						|
			return 1;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	fclose(fp);
 | 
						|
	return 0;	
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Run the +DEINSTALL script. Depending on whether this is
 | 
						|
 * a depoted package and whether this pre- or post-deinstall phase,
 | 
						|
 * different arguments are passed down.
 | 
						|
 */
 | 
						|
static int
 | 
						|
run_deinstall_script(const char *pkg, int do_postdeinstall)
 | 
						|
{
 | 
						|
	const char *target, *text;
 | 
						|
	char *fname, *fname2, *pkgdir;
 | 
						|
	int rv;
 | 
						|
 | 
						|
	fname = pkgdb_pkg_file(pkg, DEINSTALL_FNAME);
 | 
						|
	if (!fexists(fname)) {
 | 
						|
		free(fname);
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
 | 
						|
	fname2 = pkgdb_pkg_file(pkg, DEPOT_FNAME);
 | 
						|
	if (fexists(fname2)) {
 | 
						|
		if (do_postdeinstall) {
 | 
						|
			free(fname);
 | 
						|
			free(fname2);
 | 
						|
			return 0;
 | 
						|
		}
 | 
						|
		target = "VIEW-DEINSTALL";
 | 
						|
		text = "view deinstall";
 | 
						|
	} else if (do_postdeinstall) {
 | 
						|
		target = "POST-DEINSTALL";
 | 
						|
		text = "post-deinstall";
 | 
						|
	} else {
 | 
						|
		target = "DEINSTALL";
 | 
						|
		text = "deinstall";
 | 
						|
	}
 | 
						|
	free(fname2);
 | 
						|
 | 
						|
	if (Fake) {
 | 
						|
		printf("Would execute %s script with argument %s now\n",
 | 
						|
		    text, target);
 | 
						|
		free(fname);
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
 | 
						|
	pkgdir = pkgdb_pkg_dir(pkg);
 | 
						|
	if (chmod(fname, 0555))
 | 
						|
		warn("chmod of `%s' failed", fname);
 | 
						|
	rv = fcexec(pkgdir, fname, pkg, target, NULL);
 | 
						|
	if (rv)
 | 
						|
		warnx("%s script returned error status", text);
 | 
						|
	free(pkgdir);
 | 
						|
	free(fname);
 | 
						|
	return rv;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Copy lines from fname to fname_tmp, filtering out lines equal to text.
 | 
						|
 * Afterwards rename fname_tmp to fname;
 | 
						|
 */
 | 
						|
static int
 | 
						|
remove_line(const char *fname, const char *fname_tmp, const char *text)
 | 
						|
{
 | 
						|
	FILE *fp, *fp_out;
 | 
						|
	char line[MaxPathSize], *eol;
 | 
						|
	int rv;
 | 
						|
 | 
						|
	if ((fp = fopen(fname, "r")) == NULL) {
 | 
						|
		warn("Unable to open `%s'", fname);
 | 
						|
		return 1;
 | 
						|
	}
 | 
						|
	if ((fp_out = fopen(fname_tmp, "w")) == NULL) {
 | 
						|
		warn("Unable to open `%s'", fname_tmp);
 | 
						|
		fclose(fp);
 | 
						|
		return 1;
 | 
						|
	}
 | 
						|
 | 
						|
	while (fgets(line, sizeof(line), fp) != NULL) {
 | 
						|
		if ((eol = strrchr(line, '\n')) != NULL)
 | 
						|
			*eol = '\0';
 | 
						|
		if (strcmp(line, text) == 0)
 | 
						|
			continue;
 | 
						|
		fprintf(fp_out, "%s\n", line);
 | 
						|
	}
 | 
						|
	fclose(fp);
 | 
						|
 | 
						|
	if (fclose(fp_out) == EOF) {
 | 
						|
		remove(fname_tmp);
 | 
						|
		warnx("Failure while closing `%s' temp file", fname_tmp);
 | 
						|
		return 1;
 | 
						|
	}
 | 
						|
 | 
						|
	if (rename(fname_tmp, fname) == -1) {
 | 
						|
		warn("Unable to rename `%s' to `%s'", fname_tmp, fname);
 | 
						|
		rv = 1;
 | 
						|
	} else
 | 
						|
		rv = 0;
 | 
						|
	remove(fname_tmp);
 | 
						|
 | 
						|
	return rv;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Unregister the package from the depot it is registered in.
 | 
						|
 */
 | 
						|
static int
 | 
						|
remove_pkg_from_depot(const char *pkg)
 | 
						|
{
 | 
						|
	FILE *fp;
 | 
						|
	char line[MaxPathSize], *eol;
 | 
						|
	char *fname, *fname2;
 | 
						|
	int rv;
 | 
						|
 | 
						|
	fname = pkgdb_pkg_file(pkg, DEPOT_FNAME);
 | 
						|
	if (isemptyfile(fname)) {
 | 
						|
		free(fname);
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
 | 
						|
	if (Verbose)
 | 
						|
		printf("Attempting to remove the `%s' registration "
 | 
						|
		    "on package `%s'\n", fname, pkg);
 | 
						|
 | 
						|
	if (Fake) {
 | 
						|
		free(fname);
 | 
						|
		return 1;
 | 
						|
	}
 | 
						|
 | 
						|
	if ((fp = fopen(fname, "r")) == NULL) {
 | 
						|
		warn("Unable to open `%s' file", fname);
 | 
						|
		free(fname);
 | 
						|
		return 1;
 | 
						|
	}
 | 
						|
	if (fgets(line, sizeof(line), fp) == NULL) {
 | 
						|
		fclose(fp);
 | 
						|
		warnx("Empty depot file `%s'", fname);
 | 
						|
		free(fname);
 | 
						|
		return 1;
 | 
						|
	}
 | 
						|
	if ((eol = strrchr(line, '\n')) != NULL)
 | 
						|
		*eol = '\0';
 | 
						|
	fclose(fp);
 | 
						|
	free(fname);
 | 
						|
 | 
						|
	fname = pkgdb_pkg_file(pkg, VIEWS_FNAME);
 | 
						|
	fname2 = pkgdb_pkg_file(pkg, VIEWS_FNAME_TMP);
 | 
						|
	rv = remove_line(fname, fname2, line);
 | 
						|
	free(fname2);
 | 
						|
	free(fname);
 | 
						|
 | 
						|
	return rv;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * remove_depend is used as iterator function below.
 | 
						|
 * The passed-in package name should be removed from the
 | 
						|
 * +REQUIRED_BY list of the dependency.  Such an entry
 | 
						|
 * can miss in a fully correct package database, if the pattern
 | 
						|
 * matches more than one package.
 | 
						|
 */
 | 
						|
static int
 | 
						|
remove_depend(const char *cur_pkg, void *cookie)
 | 
						|
{
 | 
						|
	const char *pkg = cookie;
 | 
						|
	char *fname, *fname2;
 | 
						|
	int rv;
 | 
						|
 | 
						|
	fname = pkgdb_pkg_file(cur_pkg, REQUIRED_BY_FNAME);
 | 
						|
	if (isemptyfile(fname)) {
 | 
						|
		free(fname);
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
	fname2 = pkgdb_pkg_file(cur_pkg, REQUIRED_BY_FNAME_TMP);
 | 
						|
 | 
						|
	rv = remove_line(fname, fname2, pkg);
 | 
						|
 | 
						|
	free(fname2);
 | 
						|
	free(fname);
 | 
						|
 | 
						|
	return rv;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
remove_pkg(const char *pkg)
 | 
						|
{
 | 
						|
	FILE *fp;
 | 
						|
	char *fname, *pkgdir;
 | 
						|
	package_t plist;
 | 
						|
	plist_t *p;
 | 
						|
	int is_depoted_pkg, rv, late_error;
 | 
						|
 | 
						|
	if (pkgdb_update_only)
 | 
						|
		return pkgdb_remove_pkg(pkg) ? 0 : 1;
 | 
						|
 | 
						|
	fname = pkgdb_pkg_file(pkg, CONTENTS_FNAME);
 | 
						|
	if (!fexists(fname)) {
 | 
						|
		warnx("package `%s' is not installed, `%s' missing", pkg, fname);
 | 
						|
		free(fname);
 | 
						|
		return 1;
 | 
						|
	}
 | 
						|
	free(fname);
 | 
						|
 | 
						|
	/* +REQUIRED_BY and +PRESERVE already checked */
 | 
						|
	if (remove_pkg_from_view(pkg))
 | 
						|
		return 1;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * The views related code has bad error handling, if e.g.
 | 
						|
	 * the deinstall script fails, the package remains unregistered.
 | 
						|
	 */
 | 
						|
 | 
						|
	fname = pkgdb_pkg_file(pkg, CONTENTS_FNAME);
 | 
						|
	if ((fp = fopen(fname, "r")) == NULL) {
 | 
						|
		warnx("Failed to open `%s'", fname);
 | 
						|
		free(fname);
 | 
						|
		return 1;
 | 
						|
	}
 | 
						|
	read_plist(&plist, fp);
 | 
						|
	fclose(fp);
 | 
						|
 | 
						|
	/*
 | 
						|
	 * If a prefix has been provided, remove the first @cwd and
 | 
						|
	 * prepend that prefix.  This allows removing packages without
 | 
						|
	 * @cwd if really necessary.  pkg_admin rebuild is likely needed
 | 
						|
	 * afterwards though.
 | 
						|
	 */
 | 
						|
	if (prefix) {
 | 
						|
		delete_plist(&plist, FALSE, PLIST_CWD, NULL);
 | 
						|
		add_plist_top(&plist, PLIST_CWD, prefix);
 | 
						|
	}
 | 
						|
	if ((p = find_plist(&plist, PLIST_CWD)) == NULL) {
 | 
						|
		warnx("Package `%s' doesn't have a prefix", pkg);
 | 
						|
		return 1;
 | 
						|
	}
 | 
						|
 | 
						|
	if (find_plist(&plist, PLIST_NAME) == NULL) {
 | 
						|
		/* Cheat a bit to allow removal of such bad packages. */
 | 
						|
		warnx("Package `%s' doesn't have a name", pkg);
 | 
						|
		add_plist_top(&plist, PLIST_NAME, pkg);
 | 
						|
	}
 | 
						|
 | 
						|
	setenv(PKG_REFCOUNT_DBDIR_VNAME, config_pkg_refcount_dbdir, 1);
 | 
						|
	fname = pkgdb_pkg_dir(pkg);
 | 
						|
	setenv(PKG_METADATA_DIR_VNAME, fname, 1);
 | 
						|
	free(fname);
 | 
						|
	setenv(PKG_PREFIX_VNAME, p->name, 1);
 | 
						|
 | 
						|
	if (!no_deinstall && !unregister_only) {
 | 
						|
		if (run_deinstall_script(pkg, 0) && !Force)
 | 
						|
			return 1;
 | 
						|
	}
 | 
						|
 | 
						|
	late_error = 0;
 | 
						|
 | 
						|
	if (Fake)
 | 
						|
		printf("Attempting to delete package `%s'\n", pkg);
 | 
						|
	else if (delete_package(FALSE, &plist, unregister_only,
 | 
						|
			        destdir) == FAIL) {
 | 
						|
		warnx("couldn't entirely delete package `%s'", pkg);
 | 
						|
		/*
 | 
						|
		 * XXX It could be nice to error out here explicitly,
 | 
						|
		 * XXX but this is problematic for missing or changed files.
 | 
						|
		 * XXX At least the inability to remove files at all should
 | 
						|
		 * XXX be handled though.
 | 
						|
		 */
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Past the point of no return. Files are gone, all that is left
 | 
						|
	 * is cleaning up registered dependencies and removing the meta data.
 | 
						|
	 * Errors in the remaining part are counted, but don't stop the
 | 
						|
	 * processing.
 | 
						|
	 */
 | 
						|
 | 
						|
	fname = pkgdb_pkg_file(pkg, DEPOT_FNAME);
 | 
						|
	if (fexists(fname)) {
 | 
						|
		late_error |= remove_pkg_from_depot(pkg);
 | 
						|
		/* XXX error checking */
 | 
						|
	} else {
 | 
						|
		for (p = plist.head; p; p = p->next) {
 | 
						|
			if (p->type != PLIST_PKGDEP)
 | 
						|
				continue;
 | 
						|
			if (Verbose)
 | 
						|
				printf("Attempting to remove dependency "
 | 
						|
				    "on package `%s'\n", p->name);
 | 
						|
			if (Fake)
 | 
						|
				continue;
 | 
						|
			match_installed_pkgs(p->name, remove_depend,
 | 
						|
			    __UNCONST(pkg));
 | 
						|
		}
 | 
						|
	}
 | 
						|
	free(fname);
 | 
						|
 | 
						|
	free_plist(&plist);
 | 
						|
 | 
						|
	if (!no_deinstall && !unregister_only)
 | 
						|
		late_error |= run_deinstall_script(pkg, 1);
 | 
						|
 | 
						|
	fname = pkgdb_pkg_file(pkg, VIEWS_FNAME);
 | 
						|
	if (fexists(fname))
 | 
						|
		is_depoted_pkg = TRUE;
 | 
						|
	else
 | 
						|
		is_depoted_pkg = FALSE;
 | 
						|
	free(fname);
 | 
						|
 | 
						|
	if (Fake)
 | 
						|
		return 0;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Kill the pkgdb subdirectory. The files have been removed, so
 | 
						|
	 * this is way beyond the point of no return.
 | 
						|
	 */
 | 
						|
	pkgdir = pkgdb_pkg_dir(pkg);
 | 
						|
	(void) remove_files(pkgdir, "+*");
 | 
						|
	rv = 1;
 | 
						|
	if (isemptydir(pkgdir)&& rmdir(pkgdir) == 0)
 | 
						|
		rv = 0;
 | 
						|
	else if (is_depoted_pkg)
 | 
						|
		warnx("Depot directory `%s' is not empty", pkgdir);
 | 
						|
	else if (!Force)
 | 
						|
		warnx("Couldn't remove package directory in `%s'", pkgdir);
 | 
						|
	else if (recursive_remove(pkgdir, 1))
 | 
						|
		warn("Couldn't remove package directory `%s'", pkgdir);
 | 
						|
	else
 | 
						|
		warnx("Package directory `%s' forcefully removed", pkgdir);
 | 
						|
	free(pkgdir);
 | 
						|
 | 
						|
	return rv | late_error;
 | 
						|
}
 | 
						|
 | 
						|
int
 | 
						|
main(int argc, char *argv[])
 | 
						|
{
 | 
						|
	lpkg_head_t pkgs, sorted_pkgs;
 | 
						|
	int ch, r, has_error;
 | 
						|
	unsigned long bad_count;
 | 
						|
 | 
						|
	TAILQ_INIT(&pkgs);
 | 
						|
	TAILQ_INIT(&sorted_pkgs);
 | 
						|
 | 
						|
	setprogname(argv[0]);
 | 
						|
	while ((ch = getopt(argc, argv, "ADFfK:kNnOP:p:RrVv")) != -1) {
 | 
						|
		switch (ch) {
 | 
						|
		case 'A':
 | 
						|
			delete_automatic_leaves = 1;
 | 
						|
			break;
 | 
						|
		case 'D':
 | 
						|
			no_deinstall = 1;
 | 
						|
			break;
 | 
						|
		case 'F':
 | 
						|
			find_by_filename = 1;
 | 
						|
			break;
 | 
						|
		case 'f':
 | 
						|
			++Force;
 | 
						|
			break;
 | 
						|
		case 'K':
 | 
						|
			pkgdb_set_dir(optarg, 3);
 | 
						|
			break;
 | 
						|
		case 'k':
 | 
						|
			keep_preserve = 1;
 | 
						|
			break;
 | 
						|
		case 'N':
 | 
						|
			unregister_only = 1;
 | 
						|
			break;
 | 
						|
		case 'n':
 | 
						|
			Fake = 1;
 | 
						|
			break;
 | 
						|
		case 'O':
 | 
						|
			pkgdb_update_only = 1;
 | 
						|
			break;
 | 
						|
		case 'P':
 | 
						|
			destdir = optarg;
 | 
						|
			break;
 | 
						|
		case 'p':
 | 
						|
			prefix = optarg;
 | 
						|
			break;
 | 
						|
		case 'R':
 | 
						|
			delete_new_leaves = 1;
 | 
						|
			break;
 | 
						|
		case 'r':
 | 
						|
			delete_recursive = 1;
 | 
						|
			break;
 | 
						|
		case 'V':
 | 
						|
			show_version();
 | 
						|
			/* NOTREACHED */
 | 
						|
		case 'v':
 | 
						|
			++Verbose;
 | 
						|
			break;
 | 
						|
		default:
 | 
						|
			usage();
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	pkg_install_config();
 | 
						|
 | 
						|
	pkgdb = xstrdup(pkgdb_get_dir());
 | 
						|
 | 
						|
	if (destdir != NULL) {
 | 
						|
		char *pkgdbdir;
 | 
						|
 | 
						|
		pkgdbdir = xasprintf("%s/%s", destdir, pkgdb);
 | 
						|
		pkgdb_set_dir(pkgdbdir, 4);
 | 
						|
		free(pkgdbdir);
 | 
						|
	}
 | 
						|
 | 
						|
	argc -= optind;
 | 
						|
	argv += optind;
 | 
						|
 | 
						|
	if (argc == 0) {
 | 
						|
		if (find_by_filename)
 | 
						|
			warnx("Missing filename(s)");
 | 
						|
		else
 | 
						|
			warnx("Missing package name(s)");
 | 
						|
		usage();
 | 
						|
	}
 | 
						|
 | 
						|
	if (Fake)
 | 
						|
		r = pkgdb_open(ReadOnly);
 | 
						|
	else
 | 
						|
		r = pkgdb_open(ReadWrite);
 | 
						|
 | 
						|
	if (!r)
 | 
						|
		errx(EXIT_FAILURE, "Opening pkgdb failed");
 | 
						|
 | 
						|
	/* First, process all command line options. */
 | 
						|
 | 
						|
	has_error = 0;
 | 
						|
	for (; argc != 0; --argc, ++argv) {
 | 
						|
		if (find_by_filename)
 | 
						|
			has_error |= add_by_filename(&pkgs, *argv);
 | 
						|
		else if (ispkgpattern(*argv))
 | 
						|
			has_error |= add_by_pattern(&pkgs, *argv);
 | 
						|
		else
 | 
						|
			has_error |= add_by_pkgname(&pkgs, *argv);
 | 
						|
	}
 | 
						|
 | 
						|
	if (has_error && !Force) {
 | 
						|
		pkgdb_close();
 | 
						|
		return EXIT_FAILURE;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Second, reorder and recursive if necessary. */
 | 
						|
 | 
						|
	if (sort_and_recurse(&pkgs, &sorted_pkgs)) {
 | 
						|
		pkgdb_close();
 | 
						|
		return EXIT_FAILURE;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Third, add leaves if necessary. */
 | 
						|
 | 
						|
	if (delete_new_leaves || delete_automatic_leaves)
 | 
						|
		find_new_leaves(&sorted_pkgs);
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Now that all packages to remove are known, check
 | 
						|
	 * if all are removable.  After that, start the actual
 | 
						|
	 * removal.
 | 
						|
	 */
 | 
						|
 | 
						|
	if (find_preserve_pkgs(&sorted_pkgs)) {
 | 
						|
		pkgdb_close();
 | 
						|
		return EXIT_FAILURE;
 | 
						|
	}
 | 
						|
 | 
						|
	setenv(PKG_REFCOUNT_DBDIR_VNAME, pkgdb_refcount_dir(), 1);
 | 
						|
 | 
						|
	bad_count = 0;
 | 
						|
	while (!TAILQ_EMPTY(&sorted_pkgs)) {
 | 
						|
		lpkg_t *lpp;
 | 
						|
		
 | 
						|
		lpp = TAILQ_FIRST(&sorted_pkgs);
 | 
						|
		TAILQ_REMOVE(&sorted_pkgs, lpp, lp_link);
 | 
						|
		if (remove_pkg(lpp->lp_name)) {
 | 
						|
			++bad_count;
 | 
						|
			if (!Force)
 | 
						|
				break;
 | 
						|
		}
 | 
						|
		free_lpkg(lpp);
 | 
						|
	}
 | 
						|
 | 
						|
	pkgdb_close();
 | 
						|
 | 
						|
	if (Force && bad_count && Verbose)
 | 
						|
		warnx("Removal of %lu packages failed", bad_count);
 | 
						|
 | 
						|
	return bad_count > 0 ? EXIT_FAILURE : EXIT_SUCCESS;
 | 
						|
}
 |