OpenBSD-4.6/usr.sbin/pkg_add/pkg_add

#! /usr/bin/perl

# ex:ts=8 sw=4:
# $OpenBSD: pkg_add,v 1.336 2009/05/24 11:05:24 espie Exp $
#
# Copyright (c) 2003-2007 Marc Espie <espie@openbsd.org>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

# this is it ! The hard one
use strict;
use warnings;
use OpenBSD::Dependencies;
use OpenBSD::PackingList;
use OpenBSD::PackageInfo;
use OpenBSD::PackageLocator;
use OpenBSD::PackageName;
use OpenBSD::PkgCfl;
use OpenBSD::Vstat;
use OpenBSD::Getopt;
use OpenBSD::Error;
use OpenBSD::Interactive;
use OpenBSD::Add;
use OpenBSD::SharedLibs;
use OpenBSD::Paths;
use OpenBSD::UpdateSet;

my $bad = 0;

our %defines = ();
our $not;

package OpenBSD::pkg_add::State;
our @ISA=(qw(OpenBSD::pkg_foo::State));

# one-level dependencies tree, for nicer printouts
sub build_deptree
{
	my ($state, $pkg, @deps) = @_;

	my $tree = $state->{deptree};
	$pkg = OpenBSD::PackageName::url2pkgname($pkg);
	# flatten info
	if (defined $tree->{$pkg}) {
		$pkg = $tree->{$pkg};
	}
	for my $i (@deps) {
		my $j = $i->handle->{pkgname};
		$tree->{$j} = $pkg unless defined $tree->{$j};
	}
}

sub deptree_header
{
	my ($state, $pkg) = @_;
	if (defined $state->{deptree}->{$pkg}) {
		return $state->{deptree}->{$pkg}.':';
	} else {
		return '';
	}
}

sub mark_installed
{
	my ($state, $pkg) = @_;
	$state->{installed}->{$pkg} = 1;
}

sub is_installed
{
	my ($state, $pkg) = @_;
	return $state->{installed}->{$pkg};
}

sub installed
{
	my $state = shift;
	return keys %{$state->{installed}};
}

sub set_name_from_handle
{
	my ($state, $h) = @_;
	$state->set_pkgname($h->{pkgname});
}

package OpenBSD::UpdateSet;
use OpenBSD::PackageInfo;
use OpenBSD::Error;

sub setup_header
{
	my ($set, $state) = @_;

	my $pkgname = $set->handle->{pkgname};

	my $header = $state->deptree_header($pkgname).$pkgname;
	if ($set->older) {
		$header.=" (replacing ". join(', ', (map {$_->{pkgname}} $set->older)). ")";
	}
	if (!$state->progress->set_header($header)) {
	    print $state->{not} ? "Pretending to add " : "Adding ";
	    print $header;
	    if ($state->{do_faked}) {
		    print " under ", $state->{destdir};
	    }
	    print "\n";
	}
}

sub complete
{
	my ($set, $state) = @_;

	$set->handle->complete($state);
	return if $set->handle->has_error;
	return if defined $set->{installable};

	$set->{installable} = $set->can_install($state);

	if (!$set->{installable}) {
		$set->handle->{location}->close_with_client_error;
		$set->handle->{location}->wipe_info;
		delete $set->handle->{plist};
		$set->handle->set_error(OpenBSD::Handle::CANT_INSTALL);
	}
}

sub reorder_old
{
	my ($set, $state) = @_;
	# no need to sort things if there is one package to replace
	# return if $set->older <= 1;

	my $resolved;
	my @l = ();
	my %todo = map {($_->{pkgname}, $_)} $set->older;
	do {
		$resolved = 0;
BIGLOOP: 	while (my ($pkg, $h) = each %todo) {
			for my $name (OpenBSD::RequiredBy->new($pkg)->list) {
				next BIGLOOP if defined $todo{$name};
			}
			push(@l, $h);
			delete $todo{$pkg};
			$resolved++;
		}
	} while ($resolved);
	push(@l, values %todo);
	$set->{older} = \@l;
}

sub can_install
{
	my ($set, $state) = @_;
	my $handle = $set->handle;
	my $plist = $handle->{plist};
	my $pkgname = $handle->{pkgname};
	my @conflicts = OpenBSD::PkgCfl::find_all($plist, $state);
	return 1 if @conflicts == 0;

	my %conflicts = map {($_,1)} @conflicts;
	@conflicts = keys %conflicts;

	if (!$state->{allow_replacing}) {
		if ($state->{defines}->{conflicts}) {
			print "Forcing install of $pkgname in the presence of conflicts (",join(',', @conflicts), ")\n";
			return 1;
		}
		print "Can't install $pkgname because of conflicts (",join(',', @conflicts), ")\n";
		$handle->set_error(OpenBSD::Handle::CANT_INSTALL);
		return;
	}

	if (@conflicts >  5) {
		print "Can't install $pkgname because of conflicts (",join(',', @conflicts), ")\n";
		$handle->set_error(OpenBSD::Handle::CANT_INSTALL);
		return;
	}

	require OpenBSD::Replace;

	if (!OpenBSD::Replace::is_new_package_safe($plist, $state)) {
		print "Can't safely update to $pkgname (use -F update to force it)\n";
		$handle->set_error(OpenBSD::Handle::CANT_INSTALL);
		return;
	}

	for my $toreplace (@conflicts) {
		if ($state->is_installed($toreplace)) {
			Warn "Cannot replace $toreplace with $pkgname: just got installed\n";
			$handle->set_error(OpenBSD::Handle::CANT_INSTALL);
			return;
		}

		my $old_handle = OpenBSD::Handle->create_old($toreplace, $state);
		if ($old_handle->has_error(OpenBSD::Handle::NOT_FOUND)) {
			Fatal "Can't find $toreplace as an installed package\n";
		}
		if ($old_handle->has_error(OpenBSD::Handle::BAD_PACKAGE)) {
			Fatal "Couldn't find packing-list for $toreplace\n";
		}

		if (!OpenBSD::Replace::can_old_package_be_replaced($old_handle->{plist}, 
		    $pkgname, $state, \%conflicts)) {
			print "Can't update $toreplace into $pkgname\n";
			$handle->set_error(OpenBSD::Handle::CANT_INSTALL);
			return;
		}
		$set->add_older($old_handle);

		if ($old_handle->{plist}->has('manual-installation') && 
		    !$plist->has('manual-installation')) {
		    	OpenBSD::PackingElement::ManualInstallation->add($plist);
		}
	}
	$set->{skipupdatedeps} = \%conflicts;
	return 1;
}

package main;

sub failed_message
{
	my ($base_msg, $interrupted, @l) = @_;
	my $msg = $base_msg;
	if ($interrupted) {
		$msg = "Caught SIG$interrupted. $msg";
	}
	if (@l > 0) {
		$msg.= ", partial installation recorded as ".join(',', @l);
	}
	return $msg;
}

sub save_partial_set
{
	my ($set, $state) = @_;

	return () if $state->{not};
	my @l = ();
	for my $h ($set->newer) {
		next unless defined $h->{partial};
		push(@l, OpenBSD::Add::record_partial_installation($h->{plist}, $state, $h->{partial}));
	}
	return @l;
}

sub partial_install
{
	my ($base_msg, $set, $state) = @_;
	return failed_message($base_msg, $state->{interrupted}, save_partial_set($set, $state));
}

sub really_add
{
	my ($set, $state) = @_;

	for my $h ($set->newer) {
		$h->{plist}->set_infodir($h->{location}->info);
	}

	$set->validate_plists($state);

	my $handle = $set->handle;
	my $plist = $handle->{plist};
	my $pkgname = $handle->{pkgname};
	my $errors = 0;
	$state->set_name_from_handle($handle);
	if ($plist->is_signed) {
		if ($state->{defines}->{nosig}) {
			print STDERR "NOT CHECKING DIGITAL SIGNATURE FOR ",
				$plist->pkgname, "\n";
			$state->{check_digest} = 0;
		} else {
			require OpenBSD::x509;

			if (!OpenBSD::x509::check_signature($plist, $state)) {
				Fatal "fatal issues in ", $set->print;
			}
			$state->{check_digest} = 1;
		}
	} else {
		$state->{check_digest} = 0;
	}

	$set->setup_header($state);

	# XXX in `combined' updates, some dependencies may remove extra 
	# packages, so we do a double-take on the list of packages we 
	# are actually replacing.
	my $replacing = 0;
	if ($set->older_to_do) {
		$replacing = 1;
	} 
#	if (defined $plist->{old_libs}) {
#		$replacing = 1;
#	}
	$state->{replacing} = $replacing;

	$ENV{'PKG_PREFIX'} = $state->{localbase};

	my $handler = sub {
		$state->{interrupted} = shift;
	};
	local $SIG{'INT'} = $handler;
	local $SIG{'QUIT'} = $handler;
	local $SIG{'HUP'} = $handler;
	local $SIG{'KILL'} = $handler;
	local $SIG{'TERM'} = $handler;

	if ($replacing) {
		require OpenBSD::Replace;
		OpenBSD::Replace::save_old_libraries($set, $state);
	}

	$set->compute_size;

	if ($replacing) {
		$state->progress->set_header("$pkgname (extracting)");

		try {
			OpenBSD::Replace::perform_extraction($handle, $state);
		} catchall {
			unless ($state->{interrupted}) {
				Warn $_;
				$errors++;
			}
		};
		$state->progress->next;
		if ($state->{interrupted} || $errors) {
			Fatal partial_install("Installation of $pkgname failed",
			    $set, $state);
		}

		$set->{solver}->record_old_dependencies($state);
		for my $o ($set->older_to_do) {
			my $oldname = $o->{pkgname};
			$state->progress->set_header($oldname." (deleting)");
			$state->set_name_from_handle($o);
			require OpenBSD::Delete;
			try {
			    OpenBSD::Delete::delete_plist($o->{plist}, $state);
			} catchall {
				Warn $_;
				Fatal partial_install(
				    "Deinstallation of ".$oldname." failed", 
				    $set, $state);
			};

			if (defined $state->{updatedepends}) {
				delete $state->{updatedepends}->{$oldname};
			}
			OpenBSD::PkgCfl::unregister($o->{plist}, $state);
		}
		# Here there should be code to handle old libs

		$state->progress->set_header("$pkgname (installing)");
		$state->set_name_from_handle($handle);
	}

	try {
		OpenBSD::Add::perform_installation($handle, $state);
		if (!$state->{interrupted} && $plist->has(INSTALL)) {
			$plist->get(INSTALL)->run($state, 'POST-INSTALL');
		}
	} catchall {
		unless ($state->{interrupted}) {
			Warn $_;
			$errors++;
		}
	};
	$state->progress->next;

	unlink($plist->infodir.CONTENTS);
	if ($state->{interrupted} || $errors) {
		Fatal partial_install("Installation of $pkgname failed",
		    $set, $state);
	}
	OpenBSD::SharedLibs::add_libs_from_plist($plist);
	OpenBSD::Add::tweak_plist_status($plist, $state);
	$plist->to_cache;
	OpenBSD::Add::register_installation($plist);
	$set->{solver}->register_dependencies($state);
	add_installed($pkgname);
	delete $handle->{partial};
	OpenBSD::PkgCfl::register($plist, $state);
	if ($plist->has(DISPLAY)) {
		$plist->get(DISPLAY)->prepare($state);
	}
	$set->{solver}->adjust_old_dependencies($state);
	if ($state->{repairdependencies}) {
		$set->{solver}->repair_dependencies($state);
	}
}

sub install_set
{
	my ($set, $state, @todo) = @_;

	my $handle = $set->handle;
	if ($state->is_installed($handle->{pkgname})) {
		if (defined $handle->{location}) {
			$handle->{location}->close_now;
		}
		return ();
	}

	$set->complete($state);
	if ($handle->has_error(OpenBSD::Handle::ALREADY_INSTALLED)) {
		return ();
	}
	if ($handle->has_error) {
		if ((!$state->{defines}->{kitchensink} ||
		    $handle->has_error(OpenBSD::Handle::BAD_PACKAGE)) &&
		    !$handle->{tweaked}) {
			$state->set_name_from_handle($handle);
			$state->fatal("Fatal error");
		} else {
			return ();
		}
	}

	my $location = $handle->{location};
	my $plist = $handle->{plist};

	if ($plist->has('arch')) {
		unless ($plist->{arch}->check($state->{arch})) {
			print "$handle->{pkgname} is not for the right architecture\n";
			return () unless $defines{arch};
		}
	}
	if (!defined $set->{solver}) {
		$set->{solver} = OpenBSD::Dependencies::Solver->new($set);
		my @deps = $set->{solver}->solve_depends($state, @todo);
		if ($state->{verbose}) {
			$set->{solver}->dump;
		}
		if (@deps > 0) {
			$state->build_deptree($handle->{pkgname}, @deps);
			return (@deps, $set);
		}
	}

	# verify dependencies have been installed
	my @baddeps = $set->{solver}->check_depends;

	if (@baddeps) {
		print "Can't install $handle->{pkgname}: can't resolve ",
		    join(',', @baddeps), "\n";
		$location->close_now;
		$bad++;
		return ();
	}

#	print "Didn't need to process: ", join(',', @{$set->{solver}->{todo}}), "\n";
	if (!$set->{solver}->solve_wantlibs($state)) {
		$location->close_now;
		if (!$defines{libdepends}) {
			$bad++;
			return ();
		}
	}
#	if (!$set->{solver}->solve_tags($state)) {
#		$location->close_now;
#		if (!$defines{libdepends}) {
#			$bad++;
#			return ();
#		}
#	}
	really_add($set, $state);
	$location->wipe_info;
	delete $handle->{plist};
	$state->mark_installed($handle->{pkgname});
	return ();
}


sub find_truelocations
{
	my ($old, $new, $state) = @_;

	my $cache = {};
	for my $pkgname (@$old) {
		if (OpenBSD::PackageName::is_stem($pkgname)) {
		    my ($h, $path, $repo);
		    if ($pkgname =~ m/\//o) {
			($repo, $path, $pkgname) = OpenBSD::PackageLocator::path_parse($pkgname);
			$h = $repo;
		    } else {
			$h = 'OpenBSD::PackageLocator';
			$path = "";
		    }
		    my $l = $h->match_locations(OpenBSD::Search::Stem->new($pkgname));
		    if (@$l > 1 && !$state->{defines}->{allversions}) {
			$l = OpenBSD::Search::FilterLocation->keep_most_recent->filter_locations($l);
		    }
		    if (@$l == 0) {
		    	print "Can't resolve $pkgname\n";
			$bad = 1;
		    } elsif (@$l > 1) {
			my %h = map {($_->name, $_)} @$l;
		    	if ($state->{interactive}) {
				$h{'<None>'} = undef;
				my $result = OpenBSD::Interactive::ask_list("Ambiguous: choose package for $pkgname", $state->{interactive}, sort keys %h);
				if (defined $h{$result}) {
					push(@$new, OpenBSD::UpdateSet->from_location($h{$result}));
				} else {
					$bad = 1;
				}
			} else {
				print "Ambiguous: $pkgname could be ", 
				    join(' ', keys %h), "\n";
				$bad = 1;
			}
		    } else {
				push(@$new, OpenBSD::UpdateSet->from_location($l->[0]));
		    }
		} else {
			if (!defined $cache->{$pkgname}) {
				$cache->{$pkgname} = OpenBSD::UpdateSet->create_new($pkgname);
			}
			push(@$new, $cache->{$pkgname});
		}
	}
}

sub reorder
{
	my $l = shift;
	my $n = @$l;
	my ($a, $i, $j);
	for ($i = 0; $i < $n; $i++) {
		$j = int(rand($n-$i));
		$a = $l->[$i];
		$l->[$i] = $l->[$n-$j-1];
		$l->[$n-$j-1] = $a;
	}
}

set_usage('pkg_add [-acIinqruvx] [-A arch] [-B pkg-destdir] [-F keywords]',
'[-L localbase] [-P type] [-Q quick-destdir] pkg-name [...]');

our ($opt_a, $opt_v, $opt_n, $opt_I, $opt_L, $opt_B, $opt_A, $opt_P, $opt_Q, $opt_x, $opt_r, $opt_q, $opt_c, $opt_i, $opt_u);
$opt_v = 0;
try { 
	getopts('aqchivnruxIL:f:F:B:A:P:Q:',
	{
	 'h' => sub { Usage(); },
	 'F' => sub { 
	 		for my $o (split /\,/o, shift) { 
				$defines{$o} = 1;
			}
	    	},
	 'f' => sub { 
	 		for my $o (split /\,/o, shift) { 
				$defines{$o} = 1;
			}
	    	}}); 
} catchall {
	Usage($_);
};

try {
$opt_L = OpenBSD::Paths->localbase unless defined $opt_L;

my $state = OpenBSD::pkg_add::State->new;
$state->{cache} = {};
$state->{recorder} = OpenBSD::SharedItemsRecorder->new;
$state->{do_faked} = 0;
$state->{localbase} = $opt_L;
$state->{arch} = $opt_A;
$state->{defines} = \%defines;

if (defined $opt_Q and defined $opt_B) {
	Usage "-Q and -B are incompatible options";
}
if (defined $opt_Q and defined $opt_r) {
	Usage "-r and -Q are incompatible options";
}
if ($opt_P) {
	if ($opt_P eq 'cdrom') {
		$state->{cdrom_only} = 1;
	}
	elsif ($opt_P eq 'ftp') { 
		$state->{ftp_only} = 1;
	}
	else {
	    Usage "bad option: -P $opt_P";
	}
}
if (defined $opt_Q) {
	$state->{destdir} = $opt_Q;
	$state->{do_faked} = 1;
} elsif (defined $opt_B) {
	$state->{destdir} = $opt_B;
} elsif (defined $ENV{'PKG_PREFIX'}) {
	$state->{destdir} = $ENV{'PKG_PREFIX'};
}
if (defined $state->{destdir}) {
	$state->{destdir}.='/';
	$ENV{'PKG_DESTDIR'} = $state->{destdir};
} else {
	$state->{destdir} = '';
	delete $ENV{'PKG_DESTDIR'};
}


$state->{not} = $opt_n;
# XXX RequiredBy
$not = $opt_n;
$state->{quick} = $opt_q;
$state->{extra} = $opt_c;
$state->{automatic} = $opt_a;
$state->{dont_run_scripts} = $opt_I;
$state->{very_verbose} = $opt_v >= 2;
$state->{verbose} = $opt_v;
$state->{interactive} = $opt_i;
$state->{beverbose} = $opt_n || ($opt_v >= 2);
$state->{allow_replacing} = $opt_r || $opt_u;

if (@ARGV == 0 && !$opt_u) {
	Usage "Missing pkgname";
}

lock_db($state->{not}) unless $state->{defines}->{nolock};
$state->setup_progressmeter($opt_x);
$state->check_root;


my @todo = ();
my @todo2 = ();

if ($opt_u) {
	require OpenBSD::Update;

	if (@ARGV == 0) {
		@ARGV = sort(installed_packages());
		$state->{full_update} = 1;
	}
	my $updater = OpenBSD::Update->new;
	$updater->process(\@ARGV, $state);
	my @cantupdate = @{$updater->cant};

	if (@cantupdate > 0) {
			print "Cannot find updates for ", join(' ', @cantupdate), "\n";
		if ($state->{defines}->{alwaysupdate} ||
		    $state->{full_update} && !$state->{interactive}) {
			print "Proceeding anyways\n";
		} else {
			if (!OpenBSD::Interactive::confirm("Proceed", 
			    $state->{interactive}, 0)) {
				exit(1);
			}
	    	}
	}
	if (defined $state->{issues}) {
		print "There are some ambiguities. Please run in interactive ".
		    "mode again.\n";
	}
	@todo = @{$updater->updates};
	if (@todo > 0 && !$bad) {
		print "Running the equivalent of pkg_add -r ",
		    join(' ', (map {$_->name} @todo)), "\n";
	}
	@todo2 = map { OpenBSD::UpdateSet->from_location($_) } @todo;
} else {
	find_truelocations(\@ARGV, \@todo2, $state);
	OpenBSD::Add::tag_user_packages(@todo2);
#	if (defined $state->{defines}->{kitchensink}) {
#		reorder(\@todo);
#		if (!$opt_r) {
#			@todo = grep {s/\.tgz$//o; !is_installed($_);} @todo;
#		}
#		print "Adding in order:\n", (map { "\t$_\n" } @todo), "\n";
#	}
}

if ($bad) {
	exit(1);
}

eval {
while (my $set = shift @todo2) {
	unshift(@todo2, install_set($set, $state, @todo2));
}
};

my $dielater = $@;

$state->{recorder}->cleanup($state);
OpenBSD::PackingElement::Fontdir::finish_fontdirs($state);
OpenBSD::Add::manpages_index($state);
OpenBSD::PackingElement::Lib::ensure_ldconfig($state);

if ($state->{beverbose}) {
	OpenBSD::Vstat::tally();
}
$state->delayed_output;
if (defined $state->{updatedepends} && %{$state->{updatedepends}}) {
	print "Forced updates, bogus dependencies for ", 
	    join(' ', sort(keys %{$state->{updatedepends}})),
	    " may remain\n";
}
if (defined $state->{defines}->{kitchensink}) {
	print "Added:\n", (map { "\t$_\n" } sort $state->installed), "\n";
}
rethrow $dielater;
} catch {
	print STDERR "$0: $_\n";
	if ($_ =~ m/^Caught SIG(\w+)/o) {
		kill $1, $$;
	}
	exit(1);
};

if ($bad) {
	exit(1);
}

package OpenBSD::PackingList;

sub uses_old_libs
{
	my $plist = shift;
	require OpenBSD::RequiredBy;

	return  grep {/^\.libs\d*\-/o} 
	    OpenBSD::Requiring->new($plist->pkgname)->list;
}

sub has_new_sig
{
	my ($plist, $state) = @_;
	if (!defined $plist->{new_sig}) {
		my $n = OpenBSD::PackingList->from_installation($plist->pkgname)->signature;
		my $o = $plist->signature;
		print "Comparing full signature for ", $plist->pkgname, " \"$o\" vs. \"$n\": ", $n eq $o ? "equal\n" : "different\n" 
		    if $state->{very_verbose};
		$plist->{new_sig} = $n ne $o;
	}
	return $plist->{new_sig};
}

package OpenBSD::Handle;
use OpenBSD::PackageInfo;

sub get_plist
{
	my ($handle, $state) = @_;

	my $location = $handle->{location};
	my $pkg = $handle->{pkgname};

	if ($state->{verbose}) {
		print $state->deptree_header($pkg);
		print "parsing $pkg\n";
	}
	my $plist = $location->grabPlist;
	unless (defined $plist) {
		print "Can't find CONTENTS from ", $location->url, "\n";
		$location->close_with_client_error;
		$location->wipe_info;
		$handle->set_error(BAD_PACKAGE);
		return;
	}
	if ($plist->localbase ne $state->{localbase}) {
		print "Localbase mismatch: package has: ", $plist->localbase, " , user wants: ", $state->{localbase}, "\n";
		$location->close_with_client_error;
		$location->wipe_info;
		$handle->set_error(BAD_PACKAGE);
		return;
	}
	my $pkgname = $handle->{pkgname} = $plist->pkgname;

	if (is_installed($pkgname) && 
	    (!$state->{allow_replacing} ||	
	      !$state->{defines}->{installed} &&
	      !$plist->has_new_sig($state) && 
	      !$plist->uses_old_libs)) {
		$handle->{tweaked} = 
		    OpenBSD::Add::tweak_package_status($pkgname, $state);
		print "Not reinstalling $pkgname\n" if $state->{verbose} and
		    !$handle->{tweaked};
		$state->mark_installed($pkgname);
		$location->close_now;
		$location->wipe_info;
		$handle->set_error(ALREADY_INSTALLED);
		return;
	}
	if ($pkg ne '-') {
		if (!defined $pkgname or 
		    OpenBSD::PackageName::url2pkgname($pkg) ne $pkgname) {
			print "Package name is not consistent ???\n";
			$location->close_with_client_error;
			$location->wipe_info;
			$handle->set_error(BAD_PACKAGE);
			return;
		}
	}
	$handle->{plist} = $plist;
}

sub complete
{
	my ($handle, $state) = @_;

	return if $handle->has_error;

	my $pkgname = $handle->{pkgname};

	if (!defined $handle->{location}) {
		my $location = OpenBSD::PackageLocator->find($pkgname, 
		    $state->{arch});
		if (!$location) {
			print $state->deptree_header($pkgname);
			$handle->set_error(NOT_FOUND);
			$handle->{tweaked} = 
			    OpenBSD::Add::tweak_package_status($pkgname, 
				$state);
			if (!$handle->{tweaked}) {
				print "Can't find $pkgname\n";
			}
			return;
		}
		$handle->{location} = $location;
	}
	if (!defined $handle->{plist}) {
		$handle->get_plist($state);
	}
}