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