xref: /openbsd-src/usr.sbin/pkg_add/OpenBSD/PkgAdd.pm (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1#! /usr/bin/perl
2
3# ex:ts=8 sw=4:
4# $OpenBSD: PkgAdd.pm,v 1.90 2016/09/14 14:14:22 espie Exp $
5#
6# Copyright (c) 2003-2014 Marc Espie <espie@openbsd.org>
7#
8# Permission to use, copy, modify, and distribute this software for any
9# purpose with or without fee is hereby granted, provided that the above
10# copyright notice and this permission notice appear in all copies.
11#
12# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19
20use strict;
21use warnings;
22
23use OpenBSD::AddDelete;
24
25package OpenBSD::PackingList;
26
27sub uses_old_libs
28{
29	my $plist = shift;
30	require OpenBSD::RequiredBy;
31
32	return  grep {/^\.libs\d*\-/o}
33	    OpenBSD::Requiring->new($plist->pkgname)->list;
34}
35
36sub has_different_sig
37{
38	my ($plist, $state) = @_;
39	if (!defined $plist->{different_sig}) {
40		my $n = OpenBSD::PackingList->from_installation($plist->pkgname)->signature;
41		my $o = $plist->signature;
42		my $r = $n->compare($o, $state->defines("SHORTENED"));
43		$state->print("Comparing full signature for #1 \"#2\" vs. \"#3\":",
44		    $plist->pkgname, $o->string, $n->string)
45			if $state->verbose >= 3;
46		if (defined $r) {
47			if ($r == 0) {
48				$plist->{different_sig} = 0;
49				$state->say("equal") if $state->verbose >= 3;
50			} elsif ($r > 0) {
51				$plist->{different_sig} = 1;
52				$state->say("greater") if $state->verbose >= 3;
53			} else {
54				$plist->{different_sig} = 1;
55				$state->say("less") if $state->verbose >= 3;
56			}
57		} else {
58			$plist->{different_sig} = 1;
59			$state->say("non comparable") if $state->verbose >= 3;
60		}
61	}
62	return $plist->{different_sig};
63}
64
65package OpenBSD::PackingElement;
66sub hash_files
67{
68}
69sub tie_files
70{
71}
72
73package OpenBSD::PackingElement::FileBase;
74sub hash_files
75{
76	my ($self, $sha, $state) = @_;
77	return if $self->{link} or $self->{symlink} or $self->{nochecksum};
78	if (defined $self->{d}) {
79		$sha->{$self->{d}->key} = $self;
80	}
81}
82
83sub tie_files
84{
85	my ($self, $sha, $state) = @_;
86	return if $self->{link} or $self->{symlink} or $self->{nochecksum};
87	# XXX python doesn't like this, overreliance on timestamps
88	return if $self->{name} =~ m/\.py$/ && !defined $self->{ts};
89	if (defined $sha->{$self->{d}->key}) {
90		my $tied = $sha->{$self->{d}->key};
91		# don't tie if there's a problem with the file
92		return unless -f $tied->realname($state);
93		# and do a sanity check that this file wasn't altered
94		return unless (stat _)[7] == $self->{size};
95		$self->{tieto} = $tied;
96		$tied->{tied} = 1;
97		$state->say("Tieing #1 to #2", $self->stringize,
98		    $tied->stringize) if $state->verbose >= 3;
99	}
100}
101
102package OpenBSD::PkgAdd::State;
103our @ISA = qw(OpenBSD::AddDelete::State);
104
105sub handle_options
106{
107	my $state = shift;
108	$state->SUPER::handle_options('ruUzl:A:P:',
109	    '[-acinqrsUuvxz] [-A arch] [-B pkg-destdir] [-D name[=value]]',
110	    '[-L localbase] [-l file] [-P type] pkg-name ...');
111
112	$state->{arch} = $state->opt('A');
113
114	if ($state->opt('P')) {
115		if ($state->opt('P') eq 'cdrom') {
116			$state->{cdrom_only} = 1;
117		}
118		elsif ($state->opt('P') eq 'ftp') {
119			$state->{ftp_only} = 1;
120		}
121		else {
122		    $state->usage("bad option: -P #1", $state->opt('P'));
123		}
124	}
125	if (defined $state->opt('B')) {
126		$state->{destdir} = $state->opt('B');
127	}
128	if (defined $state->{destdir}) {
129		$state->{destdir}.='/';
130	} else {
131		$state->{destdir} = '';
132	}
133
134
135	$state->{hard_replace} = $state->opt('r');
136	$state->{newupdates} = $state->opt('u') || $state->opt('U');
137	$state->{allow_replacing} = $state->{hard_replace} ||
138	    $state->{newupdates};
139	$state->{pkglist} = $state->opt('l');
140	$state->{update} = $state->opt('u');
141	$state->{fuzzy} = $state->opt('z');
142
143	if (@ARGV == 0 && !$state->{update} && !$state->{pkglist}) {
144		$state->usage("Missing pkgname");
145	}
146}
147
148sub set_name_from_handle
149{
150	my ($state, $h, $extra) = @_;
151	$extra //= '';
152	$state->log->set_context($extra.$h->pkgname);
153}
154
155sub updateset
156{
157	my $self = shift;
158	require OpenBSD::UpdateSet;
159
160	return OpenBSD::UpdateSet->new($self);
161}
162
163sub updateset_with_new
164{
165	my ($self, $pkgname) = @_;
166
167	return $self->updateset->add_newer(
168	    OpenBSD::Handle->create_new($pkgname));
169}
170
171sub updateset_from_location
172{
173	my ($self, $location) = @_;
174
175	return $self->updateset->add_newer(
176	    OpenBSD::Handle->from_location($location));
177}
178
179sub display_timestamp
180{
181	my ($state, $pkgname, $timestamp) = @_;
182	$state->say("#1 signed on #2", $pkgname, $timestamp);
183}
184
185OpenBSD::Auto::cache(updater,
186    sub {
187	require OpenBSD::Update;
188	return OpenBSD::Update->new;
189    });
190
191OpenBSD::Auto::cache(tracker,
192    sub {
193	require OpenBSD::Tracker;
194	return OpenBSD::Tracker->new;
195    });
196
197package OpenBSD::ConflictCache;
198our @ISA = (qw(OpenBSD::Cloner));
199sub new
200{
201	my $class = shift;
202	bless {done => {}, c => {}}, $class;
203}
204
205sub add
206{
207	my ($self, $handle, $state) = @_;
208	return if $self->{done}{$handle};
209	$self->{done}{$handle} = 1;
210	for my $conflict (OpenBSD::PkgCfl::find_all($handle, $state)) {
211		$self->{c}{$conflict} = 1;
212	}
213}
214
215sub list
216{
217	my $self = shift;
218	return keys %{$self->{c}};
219}
220
221sub merge
222{
223	my ($self, @extra) = @_;
224	$self->clone('c', @extra);
225	$self->clone('done', @extra);
226}
227
228package OpenBSD::UpdateSet;
229use OpenBSD::PackageInfo;
230use OpenBSD::Error;
231use OpenBSD::Handle;
232
233OpenBSD::Auto::cache(solver,
234    sub {
235	return OpenBSD::Dependencies::Solver->new(shift);
236    });
237
238OpenBSD::Auto::cache(conflict_cache,
239    sub {
240	return OpenBSD::ConflictCache->new;
241    });
242
243sub setup_header
244{
245	my ($set, $state, $handle, $info) = @_;
246
247	my $header = $state->deptree_header($set);
248	if (defined $handle) {
249		$header .= $handle->pkgname;
250	} else {
251		$header .= $set->print;
252	}
253	if (defined $info) {
254		$header.=" ($info)";
255	}
256
257	if (!$state->progress->set_header($header)) {
258		return unless $state->verbose;
259		if (!defined $info) {
260			$header = "Adding $header";
261		}
262		if (defined $state->{lastheader} &&
263		    $header eq $state->{lastheader}) {
264			return;
265		}
266		$state->{lastheader} = $header;
267		$state->print("#1", $header);
268		$state->print("(pretending) ") if $state->{not};
269		$state->print("\n");
270	}
271}
272
273my $checked = {};
274
275sub check_security
276{
277	my ($set, $state, $plist, $h) = @_;
278	return if $checked->{$plist->fullpkgpath};
279	$checked->{$plist->fullpkgpath} = 1;
280	return if $set->{quirks};
281	my ($error, $bad);
282	$state->run_quirks(
283		sub {
284			my $quirks = shift;
285			return unless $quirks->can("check_security");
286			$bad = $quirks->check_security($plist->fullpkgpath);
287			if (defined $bad) {
288				require OpenBSD::PkgSpec;
289				my $spec = OpenBSD::PkgSpec->new($bad);
290				my $r = $spec->match_locations([$h->{location}]);
291				if (@$r != 0) {
292					$error++;
293				}
294			}
295		});
296	if ($error) {
297		$state->errsay("Package #1 found, matching insecure #2",
298		    $h->pkgname, $bad);
299	}
300}
301
302sub display_timestamp
303{
304	my ($pkgname, $plist, $state) = @_;
305
306	return unless $plist->is_signed;
307	if ($state->defines('nosig')) {
308		$state->errsay("NOT CHECKING DIGITAL SIGNATURE FOR #1",
309		    $pkgname);
310		return;
311	}
312	if (!$plist->check_signature($state)) {
313		$state->fatal("#1 is corrupted", $pkgname);
314	}
315	$state->display_timestamp($pkgname,
316	    $plist->get('digital-signature')->iso8601);
317}
318
319sub find_kept_handle
320{
321	my ($set, $n,  $state) = @_;
322	unless (defined $n->{location} && defined $n->{location}{update_info}) {
323		$n->complete($state);
324	}
325	my $plist = $n->dependency_info;
326	return if !defined $plist;
327	my $pkgname = $plist->pkgname;
328	if ($set->{quirks}) {
329		display_timestamp($pkgname, $plist, $state);
330	}
331	# condition for no update
332	unless (is_installed($pkgname) &&
333	    (!$state->{allow_replacing} ||
334	      !$state->defines('installed') &&
335	      !$plist->has_different_sig($state) &&
336	      !$plist->uses_old_libs)) {
337	      	$set->check_security($state, $plist, $n);
338	      	return;
339	}
340	my $o = $set->{older}{$pkgname};
341	if (!defined $o) {
342		$o = OpenBSD::Handle->create_old($pkgname, $state);
343		if (!defined $o->pkgname) {
344			$state->{bad}++;
345			$set->cleanup(OpenBSD::Handle::CANT_INSTALL,
346			    "Bogus package already installed");
347		    	return;
348		}
349	}
350	$set->check_security($state, $plist, $o);
351	$set->move_kept($o);
352	$o->{tweaked} =
353	    OpenBSD::Add::tweak_package_status($pkgname, $state);
354	$state->updater->progress_message($state, "No change in $pkgname");
355	delete $set->{newer}{$pkgname};
356	$n->cleanup;
357}
358
359sub figure_out_kept
360{
361	my ($set, $state) = @_;
362
363	for my $n ($set->newer) {
364		$set->find_kept_handle($n, $state);
365	}
366}
367
368sub complete
369{
370	my ($set, $state) = @_;
371
372	for my $n ($set->newer) {
373		$n->complete($state);
374		my $plist = $n->plist;
375		return 1 if !defined $plist;
376		return 1 if $n->has_error;
377	}
378	# XXX kept must have complete plists to be able to track
379	# libs for OldLibs
380	for my $o ($set->older, $set->kept) {
381		$o->complete_old($state);
382	}
383
384	my $check = $set->install_issues($state);
385	return 0 if !defined $check;
386
387	if ($check) {
388		$state->{bad}++;
389		$set->cleanup(OpenBSD::Handle::CANT_INSTALL, $check);
390		$state->tracker->cant($set);
391	}
392	return 1;
393}
394
395sub find_conflicts
396{
397	my ($set, $state) = @_;
398
399	my $c = $set->conflict_cache;
400
401	for my $handle ($set->newer) {
402		$c->add($handle, $state);
403	}
404	return $c->list;
405}
406
407sub mark_as_manual_install
408{
409	my $set = shift;
410
411	for my $handle ($set->newer) {
412		my $plist = $handle->plist;
413		$plist->has('manual-installation') or
414		    OpenBSD::PackingElement::ManualInstallation->add($plist);
415	}
416}
417
418sub updates
419{
420	my ($n, $plist) = @_;
421	if (!$n->location->update_info->match_pkgpath($plist)) {
422		return 0;
423	}
424	if (!$n->conflict_list->conflicts_with($plist->pkgname)) {
425		return 0;
426	}
427	my $r = OpenBSD::PackageName->from_string($n->pkgname)->compare(
428	    OpenBSD::PackageName->from_string($plist->pkgname));
429	if (defined $r && $r < 0) {
430		return 0;
431	}
432	return 1;
433}
434
435sub is_an_update_from
436{
437	my ($set, @conflicts) = @_;
438LOOP:	for my $c (@conflicts) {
439		next if $c =~ m/^\.libs\d*\-/;
440		next if $c =~ m/^partial\-/;
441		my $plist = OpenBSD::PackingList->from_installation($c, \&OpenBSD::PackingList::UpdateInfoOnly);
442		return 0 unless defined $plist;
443		for my $n ($set->newer) {
444			if (updates($n, $plist)) {
445				next LOOP;
446			}
447		}
448	    	return 0;
449	}
450	return 1;
451}
452
453sub install_issues
454{
455	my ($set, $state) = @_;
456
457	my @conflicts = $set->find_conflicts($state);
458
459	if (@conflicts == 0) {
460		if ($state->defines('update_only')) {
461			return "only update, no install";
462		} else {
463			return 0;
464		}
465	}
466
467	if (!$state->{allow_replacing}) {
468		if (grep { !/^\.libs\d*\-/ && !/^partial\-/ } @conflicts) {
469			if (!$set->is_an_update_from(@conflicts)) {
470				$state->errsay("Can't install #1 because of conflicts (#2)",
471				    $set->print, join(',', @conflicts));
472				return "conflicts";
473			}
474		}
475	}
476
477	my $later = 0;
478	for my $toreplace (@conflicts) {
479		if ($state->tracker->is_installed($toreplace)) {
480			$state->errsay("Cannot replace #1 in #2: just got installed",
481			    $toreplace, $set->print);
482			return "replacing just installed";
483		}
484
485		next if defined $set->{older}{$toreplace};
486		next if defined $set->{kept}{$toreplace};
487
488		$later = 1;
489		my $s = $state->tracker->is_to_update($toreplace);
490		if (defined $s && $s ne $set) {
491			$set->merge($state->tracker, $s);
492		} else {
493			$set->add_older(OpenBSD::Handle->create_old($toreplace,
494			    $state));
495		}
496	}
497
498	return if $later;
499
500
501	my $manual_install = 0;
502
503	for my $old ($set->older) {
504		my $name = $old->pkgname;
505
506		if ($old->has_error(OpenBSD::Handle::NOT_FOUND)) {
507			$state->fatal("can't find #1 in installation", $name);
508		}
509		if ($old->has_error(OpenBSD::Handle::BAD_PACKAGE)) {
510			$state->fatal("couldn't find packing-list for #1",
511			    $name);
512		}
513
514		if ($old->plist->has('manual-installation')) {
515			$manual_install = 1;
516		}
517	}
518
519	$set->mark_as_manual_install if $manual_install;
520
521	return 0;
522}
523
524sub try_merging
525{
526	my ($set, $m, $state) = @_;
527
528	my $s = $state->tracker->is_to_update($m);
529	if (!defined $s) {
530		$s = $state->updateset->add_older(
531		    OpenBSD::Handle->create_old($m, $state));
532	}
533	if ($state->updater->process_set($s, $state)) {
534		$state->say("Merging #1 (#2)", $s->print, $state->ntogo);
535		$set->merge($state->tracker, $s);
536		return 1;
537	} else {
538		$state->errsay("NOT MERGING: can't find update for #1 (#2)",
539		    $s->print, $state->ntogo);
540		return 0;
541	}
542}
543
544sub check_forward_dependencies
545{
546	my ($set, $state) = @_;
547
548	require OpenBSD::ForwardDependencies;
549	$set->{forward} = OpenBSD::ForwardDependencies->find($set);
550	my $bad = $set->{forward}->check($state);
551
552	if (%$bad) {
553		my $no_merge = 1;
554		if (!$state->defines('dontmerge')) {
555			my $okay = 1;
556			for my $m (keys %$bad) {
557				if ($set->try_merging($m, $state)) {
558					$no_merge = 0;
559				} else {
560					$okay = 0;
561				}
562			}
563			return 0 if $okay == 1;
564		}
565		if ($state->defines('updatedepends')) {
566			$state->errsay("Forcing update");
567			return $no_merge;
568		} elsif ($state->confirm("Proceed with update anyway", 0)) {
569				return $no_merge;
570		} else {
571				return undef;
572		}
573	}
574	return 1;
575}
576
577sub recheck_conflicts
578{
579	my ($set, $state) = @_;
580
581	# no conflicts between newer sets nor kept sets
582	for my $h ($set->newer, $set->kept) {
583		for my $h2 ($set->newer, $set->kept) {
584			next if $h2 == $h;
585			if ($h->conflict_list->conflicts_with($h2->pkgname)) {
586				$state->errsay("#1: internal conflict between #2 and #3",
587				    $set->print, $h->pkgname, $h2->pkgname);
588				return 0;
589			}
590		}
591	}
592
593	return 1;
594}
595
596package OpenBSD::PkgAdd;
597our @ISA = qw(OpenBSD::AddDelete);
598
599use OpenBSD::Dependencies;
600use OpenBSD::PackingList;
601use OpenBSD::PackageInfo;
602use OpenBSD::PackageName;
603use OpenBSD::PkgCfl;
604use OpenBSD::Add;
605use OpenBSD::SharedLibs;
606use OpenBSD::UpdateSet;
607use OpenBSD::Error;
608
609sub failed_message
610{
611	my ($base_msg, $received, @l) = @_;
612	my $msg = $base_msg;
613	if ($received) {
614		$msg = "Caught SIG$received. $msg";
615	}
616	if (@l > 0) {
617		$msg.= ", partial installation recorded as ".join(',', @l);
618	}
619	return $msg;
620}
621
622sub save_partial_set
623{
624	my ($set, $state) = @_;
625
626	return () if $state->{not};
627	my @l = ();
628	for my $h ($set->newer) {
629		next unless defined $h->{partial};
630		push(@l, OpenBSD::Add::record_partial_installation($h->plist, $state, $h->{partial}));
631	}
632	return @l;
633}
634
635sub partial_install
636{
637	my ($base_msg, $set, $state) = @_;
638	return failed_message($base_msg, $state->{received}, save_partial_set($set, $state));
639}
640
641sub build_before
642{
643	my %known = map {($_->pkgname, 1)} @_;
644	require OpenBSD::RequiredBy;
645	for my $c (@_) {
646		for my $d (OpenBSD::RequiredBy->new($c->pkgname)->list) {
647			push(@{$c->{before}}, $d) if $known{$d};
648		}
649	}
650}
651
652sub okay
653{
654	my ($h, $c) = @_;
655
656	for my $d (@{$c->{before}}) {
657		return 0 if !$h->{$d};
658	}
659	return 1;
660}
661
662sub iterate
663{
664	my $sub = pop @_;
665	my $done = {};
666	my $something_done;
667
668	do {
669		$something_done = 0;
670
671		for my $c (@_) {
672			next if $done->{$c->pkgname};
673			if (okay($done, $c)) {
674				&$sub($c);
675				$done->{$c->pkgname} = 1;
676				$something_done = 1;
677			}
678		}
679	} while ($something_done);
680	# if we can't do stuff in order, do it anyway
681	for my $c (@_) {
682		next if $done->{$c->pkgname};
683		&$sub($c);
684	}
685}
686
687sub check_digital_signature
688{
689	my ($set, $state) = @_;
690	for my $handle ($set->newer) {
691		$state->set_name_from_handle($handle, '+');
692		my $plist = $handle->plist;
693		my $pkgname = $plist->pkgname;
694		if ($plist->is_signed) {
695			if ($state->defines('nosig')) {
696				$state->errsay("NOT CHECKING DIGITAL SIGNATURE FOR #1",
697				    $pkgname);
698			} else {
699				if (!$plist->check_signature($state)) {
700					$state->fatal("#1 is corrupted",
701					    $pkgname);
702				}
703				$plist->{check_digest} = 1;
704				$state->{packages_with_sig}++;
705			}
706		} else {
707			$state->{packages_without_sig}{$pkgname} = 1;
708			return if $state->{signature_style} eq 'unsigned';
709			my $okay = 0;
710			my $url;
711			if (defined $handle->location) {
712				$url = $handle->location->url;
713			} else {
714				$url = $pkgname;
715			}
716			$okay = $state->confirm("UNSIGNED PACKAGE $url: install anyway", 0);
717			if (!$okay) {
718				$state->fatal("Unsigned package #1", $url);
719			}
720		}
721	}
722}
723
724sub delete_old_packages
725{
726	my ($set, $state) = @_;
727
728	build_before($set->older_to_do);
729	iterate($set->older_to_do, sub {
730		return if $state->{size_only};
731		my $o = shift;
732		$set->setup_header($state, $o, "deleting");
733		my $oldname = $o->pkgname;
734		$state->set_name_from_handle($o, '-');
735		require OpenBSD::Delete;
736		try {
737			OpenBSD::Delete::delete_plist($o->plist, $state);
738		} catchall {
739			$state->errsay($_);
740			$state->fatal(partial_install(
741			    "Deinstallation of $oldname failed",
742			    $set, $state));
743		};
744
745		if (defined $state->{updatedepends}) {
746			delete $state->{updatedepends}->{$oldname};
747		}
748		OpenBSD::PkgCfl::unregister($o, $state);
749	});
750	$set->cleanup_old_shared($state);
751	# Here there should be code to handle old libs
752}
753
754sub delayed_delete
755{
756	my $state = shift;
757	for my $realname (@{$state->{delayed}}) {
758		if (!unlink $realname) {
759			$state->errsay("Problem deleting #1: #2", $realname,
760			    $!);
761			$state->log("deleting #1 failed: #2", $realname, $!);
762		}
763	}
764	delete $state->{delayed};
765}
766
767sub really_add
768{
769	my ($set, $state) = @_;
770
771	my $errors = 0;
772
773	check_digital_signature($set, $state);
774
775	if ($state->{not}) {
776		$state->status->what("Pretending to add");
777	} else {
778		$state->status->what("Adding");
779	}
780	$set->setup_header($state);
781
782	# XXX in `combined' updates, some dependencies may remove extra
783	# packages, so we do a double-take on the list of packages we
784	# are actually replacing.
785	my $replacing = 0;
786	if ($set->older_to_do) {
787		$replacing = 1;
788	}
789	$state->{replacing} = $replacing;
790
791	my $handler = sub {
792		$state->{received} = shift;
793		$state->errsay("Interrupted");
794		if ($state->{hardkill}) {
795			delete $state->{hardkill};
796			return;
797		}
798		$state->{interrupted}++;
799	};
800	local $SIG{'INT'} = $handler;
801	local $SIG{'QUIT'} = $handler;
802	local $SIG{'HUP'} = $handler;
803	local $SIG{'KILL'} = $handler;
804	local $SIG{'TERM'} = $handler;
805
806	$state->{hardkill} = $state->{delete_first};
807
808	if ($replacing) {
809		require OpenBSD::OldLibs;
810		OpenBSD::OldLibs->save($set, $state);
811	}
812
813	if ($state->{delete_first}) {
814		delete_old_packages($set, $state);
815	}
816
817	for my $handle ($set->newer) {
818		next if $state->{size_only};
819		$set->setup_header($state, $handle, "extracting");
820
821		try {
822			OpenBSD::Add::perform_extraction($handle,
823			    $state);
824		} catchall {
825			unless ($state->{interrupted}) {
826				$state->errsay($_);
827				$errors++;
828			}
829		};
830		if ($state->{interrupted} || $errors) {
831			$state->fatal(partial_install("Installation of ".
832			    $handle->pkgname." failed", $set, $state));
833		}
834	}
835	if ($state->{delete_first}) {
836		delayed_delete($state);
837	} else {
838		$state->{hardkill} = 1;
839		delete_old_packages($set, $state);
840	}
841
842	iterate($set->newer, sub {
843		return if $state->{size_only};
844		my $handle = shift;
845		my $pkgname = $handle->pkgname;
846		my $plist = $handle->plist;
847
848		$set->setup_header($state, $handle, "installing");
849		$state->set_name_from_handle($handle, '+');
850
851		try {
852			OpenBSD::Add::perform_installation($handle, $state);
853		} catchall {
854			unless ($state->{interrupted}) {
855				$state->errsay($_);
856				$errors++;
857			}
858		};
859
860		unlink($plist->infodir.CONTENTS);
861		if ($state->{interrupted} || $errors) {
862			$state->fatal(partial_install("Installation of $pkgname failed",
863			    $set, $state));
864		}
865	});
866	$set->setup_header($state);
867	$state->progress->next($state->ntogo(-1));
868	for my $handle ($set->newer) {
869		my $pkgname = $handle->pkgname;
870		my $plist = $handle->plist;
871		OpenBSD::SharedLibs::add_libs_from_plist($plist, $state);
872		OpenBSD::Add::tweak_plist_status($plist, $state);
873		OpenBSD::Add::register_installation($plist, $state);
874		add_installed($pkgname);
875		delete $handle->{partial};
876		OpenBSD::PkgCfl::register($handle, $state);
877	}
878	delete $state->{partial};
879	$set->{solver}->register_dependencies($state);
880	if ($replacing) {
881		$set->{forward}->adjust($state);
882	}
883	if ($state->{repairdependencies}) {
884		$set->{solver}->repair_dependencies($state);
885	}
886	delete $state->{delete_first};
887	$state->syslog("Added #1", $set->print);
888	if ($state->{received}) {
889		die "interrupted";
890	}
891}
892
893sub newer_has_errors
894{
895	my ($set, $state) = @_;
896
897	for my $handle ($set->newer) {
898		if ($handle->has_error(OpenBSD::Handle::ALREADY_INSTALLED)) {
899			$set->cleanup(OpenBSD::Handle::ALREADY_INSTALLED);
900			return 1;
901		}
902		if ($handle->has_error) {
903			$state->set_name_from_handle($handle);
904			$state->log("Can't install #1: #2",
905			    $handle->pkgname, $handle->error_message);
906			$state->{bad}++;
907			$set->cleanup($handle->has_error);
908			$state->tracker->cant($set);
909			return 1;
910		}
911	}
912	return 0;
913}
914
915sub newer_is_bad_arch
916{
917	my ($set, $state) = @_;
918
919	for my $handle ($set->newer) {
920		if ($handle->plist->has('arch')) {
921			unless ($handle->plist->{arch}->check($state->{arch})) {
922				$state->set_name_from_handle($handle);
923				$state->log("#1 is not for the right architecture",
924				    $handle->pkgname);
925				if (!$state->defines('arch')) {
926					$state->{bad}++;
927					$set->cleanup(OpenBSD::Handle::CANT_INSTALL);
928					$state->tracker->cant($set);
929					return 1;
930				}
931			}
932		}
933	}
934	return 0;
935}
936
937sub may_tie_files
938{
939	my ($set, $state) = @_;
940	if ($set->newer > 0 && $set->older_to_do > 0 && !$state->defines('donttie')) {
941		my $sha = {};
942
943		for my $o ($set->older_to_do) {
944			$o->{plist}->hash_files($sha, $state);
945		}
946		for my $n ($set->newer) {
947			$n->{plist}->tie_files($sha, $state);
948		}
949	}
950}
951
952sub process_set
953{
954	my ($self, $set, $state) = @_;
955
956	$state->{current_set} = $set;
957
958	if (!$state->updater->process_set($set, $state)) {
959		return ();
960	}
961
962	for my $handle ($set->newer) {
963		if ($state->tracker->is_installed($handle->pkgname)) {
964			$set->move_kept($handle);
965			$handle->{tweaked} = OpenBSD::Add::tweak_package_status($handle->pkgname, $state);
966		}
967	}
968
969	$set->figure_out_kept($state);
970
971	if (newer_has_errors($set, $state)) {
972		return ();
973	}
974	if ($set->newer == 0 && $set->older_to_do == 0) {
975		$state->tracker->uptodate($set);
976		return ();
977	}
978
979	my @deps = $set->solver->solve_depends($state);
980	if ($state->verbose >= 2) {
981		$set->solver->dump($state);
982	}
983	if (@deps > 0) {
984		$state->build_deptree($set, @deps);
985		$set->solver->check_for_loops($state);
986		return (@deps, $set);
987	}
988
989	if (!$set->complete($state)) {
990		return $set;
991	}
992
993	if (newer_has_errors($set, $state)) {
994		return ();
995	}
996
997	for my $h ($set->newer) {
998		$set->check_security($state, $h->plist, $h);
999	}
1000
1001	if (newer_is_bad_arch($set, $state)) {
1002		return ();
1003	}
1004
1005	if ($set->older_to_do) {
1006		my $r = $set->check_forward_dependencies($state);
1007		if (!defined $r) {
1008			$state->{bad}++;
1009			$set->cleanup(OpenBSD::Handle::CANT_INSTALL);
1010			$state->tracker->cant($set);
1011			return ();
1012		}
1013		if ($r == 0) {
1014			return $set;
1015		}
1016	}
1017
1018	# verify dependencies have been installed
1019	my $baddeps = $set->solver->check_depends;
1020
1021	if (@$baddeps) {
1022		$state->errsay("Can't install #1: can't resolve #2",
1023		    $set->print, join(',', @$baddeps));
1024		$state->{bad}++;
1025		$set->cleanup(OpenBSD::Handle::CANT_INSTALL,"bad dependencies");
1026		$state->tracker->cant($set);
1027		return ();
1028	}
1029
1030	if (!$set->solver->solve_wantlibs($state)) {
1031		$state->{bad}++;
1032		$set->cleanup(OpenBSD::Handle::CANT_INSTALL, "libs not found");
1033		$state->tracker->cant($set);
1034		return ();
1035	}
1036#	if (!$set->solver->solve_tags($state)) {
1037#		if (!$state->defines('libdepends')) {
1038#			$state->{bad}++;
1039#			return ();
1040#		}
1041#	}
1042	if (!$set->recheck_conflicts($state)) {
1043		$state->{bad}++;
1044		$set->cleanup(OpenBSD::Handle::CANT_INSTALL, "fatal conflicts");
1045		$state->tracker->cant($set);
1046		return ();
1047	}
1048	if ($set->older_to_do) {
1049		require OpenBSD::Replace;
1050		if (!OpenBSD::Replace::is_set_safe($set, $state)) {
1051			$state->{bad}++;
1052			$set->cleanup(OpenBSD::Handle::CANT_INSTALL, "exec detected");
1053			$state->tracker->cant($set);
1054			return ();
1055		}
1056	}
1057	may_tie_files($set, $state);
1058	if ($set->newer > 0 || $set->older_to_do > 0) {
1059		for my $h ($set->newer) {
1060			$h->plist->set_infodir($h->location->info);
1061			delete $h->location->{contents};
1062		}
1063
1064		if (!$set->validate_plists($state)) {
1065			$state->{bad}++;
1066			$set->cleanup(OpenBSD::Handle::CANT_INSTALL,
1067			    "file issues");
1068			$state->tracker->cant($set);
1069			return ();
1070		}
1071
1072		really_add($set, $state);
1073	}
1074	$set->cleanup;
1075	$state->tracker->done($set);
1076	return ();
1077}
1078
1079sub inform_user_of_problems
1080{
1081	my $state = shift;
1082	my @cantupdate = $state->tracker->cant_list;
1083	if (@cantupdate > 0) {
1084		$state->run_quirks(
1085		    sub {
1086		    	my $quirks = shift;
1087			$quirks->filter_obsolete(\@cantupdate, $state);
1088		    });
1089
1090		$state->say("Couldn't find updates for #1",
1091		    join(' ', sort @cantupdate)) if @cantupdate > 0;
1092	}
1093	if (defined $state->{issues}) {
1094		$state->say("There were some ambiguities. ".
1095		    "Please run in interactive mode again.");
1096	}
1097}
1098
1099# if we already have quirks, we update it. If not, we try to install it.
1100sub quirk_set
1101{
1102	my $state = shift;
1103	require OpenBSD::Search;
1104
1105	my $set = $state->updateset;
1106	$set->{quirks} = 1;
1107	my $l = $state->repo->installed->match_locations(OpenBSD::Search::Stem->new('quirks'));
1108	if (@$l > 0) {
1109		$set->add_older(map {OpenBSD::Handle->from_location($_)} @$l);
1110	} else {
1111		$set->add_hints2('quirks');
1112	}
1113	return $set;
1114}
1115
1116sub do_quirks
1117{
1118	my ($self, $state) = @_;
1119	my $set = quirk_set($state);
1120	$self->process_set($set, $state);
1121}
1122
1123
1124sub process_parameters
1125{
1126	my ($self, $state) = @_;
1127	my $add_hints = $state->{fuzzy} ? "add_hints" : "add_hints2";
1128
1129	# match against a list
1130	if ($state->{pkglist}) {
1131		open my $f, '<', $state->{pkglist} or
1132		    $state->fatal("bad list #1: #2", $state->{pkglist}, $!);
1133		while (<$f>) {
1134			chomp;
1135			s/\s.*//;
1136			s/\.tgz$//;
1137			push(@{$state->{setlist}},
1138			    $state->updateset->$add_hints($_));
1139		}
1140	}
1141
1142	# update existing stuff
1143	if ($state->{update}) {
1144
1145		if (@ARGV == 0) {
1146			@ARGV = sort(installed_packages());
1147			$state->{allupdates} = 1;
1148		}
1149		my $inst = $state->repo->installed;
1150		for my $pkgname (@ARGV) {
1151			my $l;
1152
1153			next if $pkgname =~ m/^quirks\-\d/;
1154			if (OpenBSD::PackageName::is_stem($pkgname)) {
1155				$l = $state->updater->stem2location($inst, $pkgname, $state);
1156			} else {
1157				$l = $inst->find($pkgname);
1158			}
1159			if (!defined $l) {
1160				$state->say("Problem finding #1", $pkgname);
1161			} else {
1162				push(@{$state->{setlist}},
1163				    $state->updateset->add_older(OpenBSD::Handle->from_location($l)));
1164			}
1165		}
1166	} else {
1167
1168	# actual names
1169		for my $pkgname (@ARGV) {
1170			next if $pkgname =~ m/^quirks\-\d/;
1171			push(@{$state->{setlist}},
1172			    $state->updateset->$add_hints($pkgname));
1173		}
1174	}
1175}
1176
1177sub finish_display
1178{
1179	my ($self, $state) = @_;
1180	OpenBSD::Add::manpages_index($state);
1181
1182	# and display delayed thingies.
1183	my $warn = 1;
1184	if ($state->{signature_style} eq 'unsigned') {
1185		$warn = 0;
1186	}
1187	if ($state->{packages_with_sig}) {
1188		$warn = 1;
1189	}
1190	if ($warn && $state->{packages_without_sig}) {
1191		$state->say("UNSIGNED PACKAGES: #1",
1192		    join(', ', keys %{$state->{packages_without_sig}}));
1193	}
1194	if (defined $state->{updatedepends} && %{$state->{updatedepends}}) {
1195		$state->say("Forced updates, bogus dependencies for ",
1196		    join(' ', sort(keys %{$state->{updatedepends}})),
1197		    " may remain");
1198	}
1199	inform_user_of_problems($state);
1200}
1201
1202sub tweak_list
1203{
1204	my ($self, $state) = @_;
1205
1206	$state->run_quirks(
1207	    sub {
1208	    	my $quirks = shift;
1209		$quirks->tweak_list($state->{setlist}, $state);
1210	    });
1211}
1212
1213sub main
1214{
1215	my ($self, $state) = @_;
1216
1217	$state->progress->set_header('');
1218	$self->do_quirks($state);
1219
1220	$self->process_setlist($state);
1221}
1222
1223
1224sub new_state
1225{
1226	my ($self, $cmd) = @_;
1227	return OpenBSD::PkgAdd::State->new($cmd);
1228}
1229
12301;
1231