xref: /openbsd/usr.sbin/pkg_add/OpenBSD/Update.pm (revision 35d93a1a)
1# ex:ts=8 sw=4:
2# $OpenBSD: Update.pm,v 1.171 2023/10/07 09:10:03 espie Exp $
3#
4# Copyright (c) 2004-2014 Marc Espie <espie@openbsd.org>
5#
6# Permission to use, copy, modify, and distribute this software for any
7# purpose with or without fee is hereby granted, provided that the above
8# copyright notice and this permission notice appear in all copies.
9#
10# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16
17use v5.36;
18
19package OpenBSD::Handle;
20sub update($self, $updater, $set, $state)
21{
22
23	return $updater->process_handle($set, $self, $state);
24}
25
26# TODO hint and hint2 are horrible names
27package OpenBSD::hint;
28sub update($self, $updater, $set, $state)
29{
30
31	return $updater->process_hint($set, $self, $state);
32}
33
34package OpenBSD::hint2;
35sub update($self, $updater, $set, $state)
36{
37	return $updater->process_hint2($set, $self, $state);
38}
39
40package OpenBSD::Update;
41use OpenBSD::PackageInfo;
42use OpenBSD::PackageName;
43use OpenBSD::Error;
44use OpenBSD::UpdateSet;
45
46sub new($class)
47{
48	return bless {}, $class;
49}
50
51sub add_handle($self, $set, $old, $n)
52{
53	$old->{update_found} = $n;
54	$set->add_newer($n);
55}
56
57sub add_location($self, $set, $handle, $location)
58{
59	$self->add_handle($set, $handle,
60	    OpenBSD::Handle->from_location($location));
61}
62
63sub look_for_debug($self, $set, $oldname, $newname, $state)
64{
65	# hurdles to pass before adding debug packages
66	return unless $state->{debug_packages};
67
68	return if $state->tracker->is_to_update("debug-".$oldname);
69	my $dbg = "debug-".$newname;
70	my $l = $set->match_locations(OpenBSD::Search::Exact->new($dbg));
71	# TODO if @$l == 0, I should look for other packages with similar names
72	# just so I can warn for out-of-date/shearing in the mirrors.
73	return if @$l != 1;
74	$set->add_newer(OpenBSD::Handle->from_location($l->[0]));
75}
76
77sub found_update($self, $set, $old, $location, $state)
78{
79	$self->add_location($set, $old, $location);
80	$self->look_for_debug($set, $old->pkgname, $location->name, $state);
81}
82
83sub progress_message($self, $state, @r)
84{
85	my $msg = $state->f(@r);
86	$msg .= $state->ntogo_string;
87	$state->progress->message($msg);
88	$state->say($msg) if $state->verbose >= 2;
89}
90
91sub process_handle($self, $set, $h, $state)
92{
93	my $pkgname = $h->pkgname;
94
95	if ($pkgname =~ m/^\.libs\d*\-/o) {
96		return 0;
97	}
98
99	if (!$set->{quirks}) {
100		my $base = 0;
101		$state->run_quirks(
102		    sub($quirks) {
103			$base = $quirks->is_base_system($h, $state);
104		    });
105		if ($base) {
106			$h->{update_found} = OpenBSD::Handle->system;
107			$set->{updates}++;
108			return 1;
109		}
110	}
111
112	my $plist = OpenBSD::PackingList->from_installation($pkgname,
113	    \&OpenBSD::PackingList::UpdateInfoOnly);
114	if (!defined $plist) {
115		$state->fatal("can't locate #1", $pkgname);
116	}
117
118	if ($plist->has('firmware') && !$state->defines('FW_UPDATE')) {
119		$set->move_kept($h);
120		$h->{is_firmware} = 1;
121		return 0;
122	}
123
124#	if (defined $plist->{url}) {
125#		my $repo;
126#		($repo, undef) = $state->repo->path_parse($plist->{url}->name);
127#		$set->add_repositories($repo);
128#	}
129	my @search = ();
130
131	my $sname = $pkgname;
132	while ($sname =~ s/^partial\-//o) {
133	}
134	push(@search, OpenBSD::Search::Stem->split($sname));
135
136	if (!$set->{quirks}) {
137		$state->run_quirks(
138		    sub($quirks) {
139			$quirks->tweak_search(\@search, $h, $state);
140		    });
141	}
142	my $oldfound = 0;
143	my @skipped_locs = ();
144
145	# XXX this is nasty: maybe we added an old set to update
146	# because of conflicts, in which case the pkgpath +
147	# conflict should be enough  to "match".
148	for my $n ($set->newer) {
149		if (($state->{hard_replace} ||
150		    $n->location->update_info->match_pkgpath($plist)) &&
151			$n->conflict_list->conflicts_with($sname)) {
152				$self->add_handle($set, $h, $n);
153				return 1;
154		}
155	}
156	# XXX all that code conveniently forgets about old versions, while
157	# marking them as "normal".
158	# there should be some error path when we consistently fail to find
159	# an equal-or-newer version in our repository, so that pkg_add has
160	# consistent exit codes.
161	if (!$state->defines('downgrade')) {
162		push(@search, OpenBSD::Search::FilterLocation->more_recent_than($sname, \$oldfound));
163	}
164	push(@search, OpenBSD::Search::FilterLocation->new(
165	    sub($l) {
166		if (@$l == 0) {
167			return $l;
168		}
169		my @l2 = ();
170		for my $loc (@$l) {
171		    if (!$loc) {
172			    next;
173		    }
174		    my $p2 = $loc->update_info;
175		    if (!$p2) {
176			next;
177		    }
178		    if ($p2->has('arch')) {
179			unless ($p2->{arch}->check($state->{arch})) {
180			    $loc->forget;
181			    next;
182			}
183		    }
184		    if (!$plist->match_pkgpath($p2)) {
185			push(@skipped_locs, $loc);
186			next
187		    }
188		    my $r = $plist->signature->compare($p2->signature, $state);
189		    if (defined $r && $r > 0 && !$state->defines('downgrade')) {
190		    	$oldfound = 1;
191			$loc->forget;
192			next;
193		    }
194		    push(@l2, $loc);
195		}
196		return \@l2;
197	    }));
198
199	if (!$state->defines('allversions')) {
200		push(@search, OpenBSD::Search::FilterLocation->keep_most_recent);
201	}
202
203	my $l = $set->match_locations(@search);
204
205	for my $loc (@skipped_locs) {
206		if (@$l == 0 && $state->verbose) {
207			$self->say_skipped_packages($state, $plist,
208				$loc->update_info);
209		}
210		$loc->forget;
211	}
212
213	if (@$l == 0) {
214		if ($oldfound) {
215			$set->move_kept($h);
216			$self->progress_message($state,
217			    "No need to update #1", $pkgname);
218			$self->look_for_debug($set, $pkgname, $pkgname, $state);
219			return 0;
220		}
221		return undef;
222	}
223	$state->say("Update candidates: #1 -> #2#3", $pkgname,
224	    join(' ', map {$_->name} @$l), $state->ntogo_string)
225		if $state->verbose;
226
227	my $r = $state->choose_location($pkgname, $l);
228	if (defined $r) {
229		$self->found_update($set, $h, $r, $state);
230		return 1;
231	} else {
232		$state->{issues} = 1;
233		return undef;
234	}
235}
236
237sub say_skipped_packages($self, $state, $o, $n)
238{
239	my $o_name = $o->pkgname;
240	my @o_ps = map { @{$o->pkgpath->{$_}} } keys %{$o->pkgpath};
241	my $o_pp = join(" ", map {$_->fullpkgpath} @o_ps);
242
243	my $n_name = $n->pkgname;
244	my @n_ps = map { @{$n->pkgpath->{$_}} } keys %{$n->pkgpath};
245	my $n_pp= join(" ", map {$_->fullpkgpath} @n_ps);
246
247	my $t  = "Skipping #1 (update candidate for #2)";
248	   $t .= "\n\t#2 pkgpaths: #4\n\t#1 pkgpaths: #3";
249
250	$state->say($t, $n_name, $o_name, $n_pp, $o_pp);
251}
252
253sub find_nearest($base, $locs)
254{
255	my $pkgname = OpenBSD::PackageName->from_string($base);
256	return undef if !defined $pkgname->{version};
257	my @sorted = sort {$a->pkgname->{version}->compare($b->pkgname->{version}) } @$locs;
258	if ($sorted[0]->pkgname->{version}->compare($pkgname->{version}) > 0) {
259		return $sorted[0];
260	}
261	if ($sorted[-1]->pkgname->{version}->compare($pkgname->{version}) < 0) {
262		return $sorted[-1];
263	}
264	return undef;
265}
266
267sub process_hint($self, $set, $hint, $state)
268{
269	my $l;
270	my $hint_name = $hint->pkgname;
271	my $k = OpenBSD::Search::FilterLocation->keep_most_recent;
272	# first try to find us exactly
273
274	$self->progress_message($state, "Looking for #1", $hint_name);
275	$l = $set->match_locations(OpenBSD::Search::Exact->new($hint_name), $k);
276	if (@$l == 0) {
277		my $t = $hint_name;
278		$t =~ s/\-\d([^-]*)\-?/--/;
279		my @search = (OpenBSD::Search::Stem->new($t));
280		$state->run_quirks(
281		    sub($quirks) {
282			$quirks->tweak_search(\@search, $hint, $state);
283		    });
284		$l = $set->match_locations(@search, $k);
285	}
286	if (@$l > 1) {
287		my $r = find_nearest($hint_name, $l);
288		if (defined $r) {
289			$self->found_update($set, $hint, $r, $state);
290			return 1;
291		}
292	}
293	my $r = $state->choose_location($hint_name, $l);
294	if (defined $r) {
295		$self->found_update($set, $hint, $r, $state);
296		OpenBSD::Add::tag_user_packages($set);
297		return 1;
298	} else {
299		return 0;
300	}
301}
302
303my $cache = {};
304
305sub process_hint2($self, $set, $hint, $state)
306{
307	my $pkgname = $hint->pkgname;
308	my $pkg2;
309	if ($pkgname =~ m/[\/\:]/o) {
310		my $repo;
311		($repo, $pkg2) = $state->repo->path_parse($pkgname);
312		$set->add_repositories($repo);
313	} else {
314		$pkg2 = $pkgname;
315	}
316	if (OpenBSD::PackageName::is_stem($pkg2)) {
317		my $l = $state->updater->stem2location($set, $pkg2, $state,
318		    $set->{quirks});
319		if (defined $l) {
320			$self->add_location($set, $hint, $l);
321			$self->look_for_debug($set, $l->name, $l->name, $state);
322		} else {
323			return undef;
324		}
325	} else {
326		if (!defined $cache->{$pkgname}) {
327			$self->add_handle($set, $hint, OpenBSD::Handle->create_new($pkgname));
328			$cache->{$pkgname} = 1;
329			$pkg2 =~ s/\.tgz$//;
330			$self->look_for_debug($set, $pkg2, $pkg2, $state);
331		}
332	}
333	OpenBSD::Add::tag_user_packages($set);
334	return 1;
335}
336
337sub process_set($self, $set, $state)
338{
339	my @problems = ();
340	for my $h ($set->older, $set->hints) {
341		next if $h->{update_found};
342		if (!defined $h->update($self, $set, $state)) {
343			push(@problems, $h->pkgname);
344		}
345	}
346	if (@problems > 0) {
347		$state->tracker->cant($set) if !$set->{quirks};
348		if ($set->{updates} != 0) {
349			$state->say("Can't update #1: no update found for #2",
350			    $set->print, join(',', @problems));
351		}
352		return 0;
353	} elsif ($set->{updates} == 0) {
354		$state->tracker->uptodate($set);
355		return 0;
356	}
357	$state->tracker->add_set($set);
358	return 1;
359}
360
361sub stem2location($self, $locator, $name, $state, $is_quirks = 0)
362{
363	my $l = $locator->match_locations(OpenBSD::Search::Stem->new($name));
364	if (@$l > 1 && !$state->defines('allversions')) {
365		$l = OpenBSD::Search::FilterLocation->keep_most_recent->filter_locations($l);
366	}
367	return $state->choose_location($name, $l, $is_quirks);
368}
369
3701;
371