1a409537dSespie#!/usr/bin/perl
2a409537dSespie# ex:ts=8 sw=4:
3*ab16e3ccSespie# $OpenBSD: PkgDelete.pm,v 1.51 2023/10/09 07:12:22 espie Exp $
4a409537dSespie#
5a409537dSespie# Copyright (c) 2003-2010 Marc Espie <espie@openbsd.org>
6a409537dSespie#
7a409537dSespie# Permission to use, copy, modify, and distribute this software for any
8a409537dSespie# purpose with or without fee is hereby granted, provided that the above
9a409537dSespie# copyright notice and this permission notice appear in all copies.
10a409537dSespie#
11a409537dSespie# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12a409537dSespie# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13a409537dSespie# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14a409537dSespie# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15a409537dSespie# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16a409537dSespie# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17a409537dSespie# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18a409537dSespie
19039cbdaaSespieuse v5.36;
20a409537dSespie
21a409537dSespieuse OpenBSD::AddDelete;
22a409537dSespie
23295c9761Sespie
24681090d4Sespiepackage OpenBSD::PkgDelete::Tracker;
25681090d4Sespie
26039cbdaaSespiesub new($class)
27681090d4Sespie{
28681090d4Sespie	bless {}, $class;
29681090d4Sespie}
30681090d4Sespie
31039cbdaaSespiesub sets_todo($self, $offset = 0)
32681090d4Sespie{
33681090d4Sespie	return sprintf("%u/%u", (scalar keys %{$self->{done}})-$offset,
34681090d4Sespie		scalar keys %{$self->{total}});
35681090d4Sespie}
36681090d4Sespie
37039cbdaaSespiesub handle_set($self, $set)
38681090d4Sespie{
39681090d4Sespie	$self->{total}{$set} = 1;
40681090d4Sespie	if ($set->{finished}) {
41681090d4Sespie		$self->{done}{$set} = 1;
42681090d4Sespie	}
43681090d4Sespie}
44681090d4Sespie
45039cbdaaSespiesub todo($self, @list)
46681090d4Sespie{
4746412527Sespie	for my $set (@list) {
48681090d4Sespie		for my $pkgname ($set->older_names) {
49681090d4Sespie			$self->{todo}{$pkgname} = $set;
50681090d4Sespie		}
51681090d4Sespie		$self->handle_set($set);
52681090d4Sespie	}
53681090d4Sespie}
54681090d4Sespie
55681090d4Sespie
56039cbdaaSespiesub done($self, $set)
57681090d4Sespie{
58681090d4Sespie	$set->{finished} = 1;
59681090d4Sespie	for my $pkgname ($set->older_names) {
60681090d4Sespie		delete $self->{todo}{$pkgname};
61681090d4Sespie	}
62681090d4Sespie	$self->handle_set($set);
63681090d4Sespie}
64681090d4Sespie
65039cbdaaSespiesub cant	# forwarder
6646412527Sespie{
6746412527Sespie	&done;
6846412527Sespie}
69039cbdaaSespie
70039cbdaaSespiesub find($self, $pkgname)
71681090d4Sespie{
72681090d4Sespie	return $self->{todo}{$pkgname};
73681090d4Sespie}
74681090d4Sespie
75681090d4Sespie
76681090d4Sespie
77a409537dSespiepackage OpenBSD::PkgDelete::State;
78a409537dSespieour @ISA = qw(OpenBSD::AddDelete::State);
79a409537dSespie
80039cbdaaSespiesub new($class, @p)
81681090d4Sespie{
82039cbdaaSespie	my $self = $class->SUPER::new(@p);
83681090d4Sespie	$self->{tracker} = OpenBSD::PkgDelete::Tracker->new;
84681090d4Sespie	return $self;
85681090d4Sespie}
86681090d4Sespie
87039cbdaaSespiesub tracker($self)
88681090d4Sespie{
89681090d4Sespie	return $self->{tracker};
90681090d4Sespie}
91681090d4Sespie
92039cbdaaSespiesub handle_options($state)
938a660df1Sespie{
94bd849d19Sespie	$state->SUPER::handle_options('X',
9552fcbadbSespie	    '[-acimnqsVvXx] [-B pkg-destdir] [-D name[=value]] [pkg-name ...]');
968a660df1Sespie
97bd849d19Sespie	$state->{exclude} = $state->opt('X');
988a660df1Sespie}
998a660df1Sespie
100039cbdaaSespiesub stem2location($self, $locator, $name, $state)
101681090d4Sespie{
102681090d4Sespie	require OpenBSD::Search;
103681090d4Sespie	my $l = $locator->match_locations(OpenBSD::Search::Stem->new($name));
104681090d4Sespie	if (@$l > 1 && !$state->defines('allversions')) {
105681090d4Sespie		$l = OpenBSD::Search::FilterLocation->keep_most_recent->filter_locations($l);
106681090d4Sespie	}
107681090d4Sespie	return $state->choose_location($name, $l);
108a409537dSespie}
109a409537dSespie
110039cbdaaSespiesub deleteset($self)
111303a35c3Sespie{
112303a35c3Sespie	require OpenBSD::UpdateSet;
113303a35c3Sespie
114303a35c3Sespie	return OpenBSD::DeleteSet->new($self);
115303a35c3Sespie}
116303a35c3Sespie
117039cbdaaSespiesub deleteset_from_location($self, $location)
118303a35c3Sespie{
119303a35c3Sespie	return $self->deleteset->add_older(OpenBSD::Handle->from_location($location));
120303a35c3Sespie}
121303a35c3Sespie
122039cbdaaSespiesub solve_dependency($self, $solver, $dep, $package)
1235c974d94Sespie{
1245c974d94Sespie	# simpler dependency solving
1255c974d94Sespie	return $solver->find_dep_in_installed($self, $dep);
1265c974d94Sespie}
1275c974d94Sespie
128d2f781d7Sespiepackage OpenBSD::DeleteSet;
129039cbdaaSespiesub setup_header($set, $state, $handle = undef)
130d2f781d7Sespie{
131d2f781d7Sespie	my $header = $state->deptree_header($set);
132d2f781d7Sespie	if (defined $handle) {
133d2f781d7Sespie		$header .= $handle->pkgname;
134d2f781d7Sespie	} else {
135d2f781d7Sespie		$header .= $set->print;
136d2f781d7Sespie	}
137d2f781d7Sespie	if (!$state->progress->set_header($header)) {
138d2f781d7Sespie		return unless $state->verbose;
139d2f781d7Sespie		$header = "Deleting $header";
140d2f781d7Sespie		if (defined $state->{lastheader} &&
141d2f781d7Sespie		    $header eq $state->{lastheader}) {
142d2f781d7Sespie			return;
143d2f781d7Sespie		}
144d2f781d7Sespie		$state->{lastheader} = $header;
145d2f781d7Sespie		$state->print("#1", $header);
146d2f781d7Sespie		$state->print("(pretending) ") if $state->{not};
147d2f781d7Sespie		$state->print("\n");
148d2f781d7Sespie	}
149d2f781d7Sespie}
150d2f781d7Sespie
151a409537dSespiepackage OpenBSD::PkgDelete;
152a409537dSespieour @ISA = qw(OpenBSD::AddDelete);
153a409537dSespie
154a409537dSespieuse OpenBSD::PackingList;
155a409537dSespieuse OpenBSD::RequiredBy;
156a409537dSespieuse OpenBSD::Delete;
157a409537dSespieuse OpenBSD::PackageInfo;
158a409537dSespieuse OpenBSD::UpdateSet;
159681090d4Sespieuse OpenBSD::Handle;
160a409537dSespie
161a409537dSespie
162039cbdaaSespiesub add_location($self, $state, $l)
163681090d4Sespie{
164681090d4Sespie	push(@{$state->{setlist}},
165303a35c3Sespie	    $state->deleteset_from_location($l));
166681090d4Sespie}
167681090d4Sespie
168039cbdaaSespiesub create_locations($state, @l)
169681090d4Sespie{
170681090d4Sespie	my $inst = $state->repo->installed;
171681090d4Sespie	my $result = [];
172681090d4Sespie	for my $name (@l) {
173f6c952a4Sespie		my $l = $inst->find($name);
174681090d4Sespie		if (!defined $l) {
175681090d4Sespie			$state->errsay("Can't find #1 in installed packages",
176681090d4Sespie			    $name);
177681090d4Sespie			$state->{bad}++;
178681090d4Sespie		} else {
179303a35c3Sespie			push(@$result, $state->deleteset_from_location($l));
180681090d4Sespie		}
181681090d4Sespie	}
182681090d4Sespie	return $result;
183681090d4Sespie}
184681090d4Sespie
185039cbdaaSespiesub process_parameters($self, $state)
186a409537dSespie{
187681090d4Sespie	my $inst = $state->repo->installed;
1887096cf21Sespie
189fef35309Sespie	if (@ARGV == 0) {
19053a284c3Sespie		if (!($state->{automatic} || $state->{exclude})) {
191c864de0cSdv			$state->usage("No packages to delete");
192a409537dSespie		}
193a409537dSespie	} else {
194681090d4Sespie		for my $pkgname (@ARGV) {
195681090d4Sespie			my $l;
196681090d4Sespie
197681090d4Sespie			if (OpenBSD::PackageName::is_stem($pkgname)) {
198fef35309Sespie				$l = $state->stem2location($inst, $pkgname,
199fef35309Sespie				    $state);
200681090d4Sespie			} else {
201db099e94Sespie				$l = $inst->find($pkgname);
202681090d4Sespie			}
203681090d4Sespie			if (!defined $l) {
204295c9761Sespie				unless ($state->{exclude}) {
20545d818e9Sespie					$state->say("Problem finding #1",
20645d818e9Sespie					    $pkgname);
207a409537dSespie					$state->{bad}++;
208295c9761Sespie				}
209681090d4Sespie			} else {
210681090d4Sespie				$self->add_location($state, $l);
211a409537dSespie			}
212a409537dSespie		}
213681090d4Sespie	}
214a409537dSespie}
215a409537dSespie
216039cbdaaSespiesub finish_display($, $)
217a409537dSespie{
218a409537dSespie}
219a409537dSespie
220039cbdaaSespiesub really_remove($set, $state)
221681090d4Sespie{
222681090d4Sespie	if ($state->{not}) {
223681090d4Sespie		$state->status->what("Pretending to delete");
224681090d4Sespie	} else {
225681090d4Sespie		$state->status->what("Deleting");
226681090d4Sespie	}
227d2f781d7Sespie	$set->setup_header($state);
228a0a2ed7eSespie	for my $pkg ($set->older) {
229a0a2ed7eSespie		$set->setup_header($state, $pkg);
230a0a2ed7eSespie		$state->log->set_context('-'.$pkg->pkgname);
231a0a2ed7eSespie		OpenBSD::Delete::delete_handle($pkg, $state);
232681090d4Sespie	}
233681090d4Sespie	$state->progress->next($state->ntogo);
234de512720Sespie	$state->syslog("Removed #1", $set->print);
235681090d4Sespie}
236681090d4Sespie
237039cbdaaSespiesub delete_dependencies($state)
238f1ddee08Sespie{
239f1ddee08Sespie	if ($state->defines("dependencies")) {
240f1ddee08Sespie		return 1;
241f1ddee08Sespie	}
242fb75580aSespie	return $state->confirm_defaults_to_no("Delete them as well");
243f1ddee08Sespie}
244f1ddee08Sespie
245039cbdaaSespiesub fix_bad_dependencies($state)
246f1ddee08Sespie{
247f1ddee08Sespie	if ($state->defines("baddepend")) {
248f1ddee08Sespie		return 1;
249f1ddee08Sespie	}
250fb75580aSespie	return $state->confirm_defaults_to_no("Delete anyway");
251f1ddee08Sespie}
252f1ddee08Sespie
253039cbdaaSespiesub process_set($self, $set, $state)
254681090d4Sespie{
255681090d4Sespie	my $todo = {};
256681090d4Sespie	my $bad = {};
257681090d4Sespie    	for my $pkgname ($set->older_names) {
258681090d4Sespie		unless (is_installed($pkgname)) {
259681090d4Sespie			$state->errsay("#1 was not installed", $pkgname);
260681090d4Sespie			$set->{finished} = 1;
261681090d4Sespie			$set->cleanup(OpenBSD::Handle::NOT_FOUND);
262681090d4Sespie			$state->{bad}++;
263681090d4Sespie			return ();
264681090d4Sespie		}
265681090d4Sespie		my $r = OpenBSD::RequiredBy->new($pkgname);
266681090d4Sespie		for my $pkg ($r->list) {
267a0a2ed7eSespie			next if $set->{older}{$pkg};
268681090d4Sespie			my $f = $state->tracker->find($pkg);
269681090d4Sespie			if (defined $f) {
270681090d4Sespie				$todo->{$pkg} = $f;
271681090d4Sespie			} else {
272681090d4Sespie				$bad->{$pkg} = 1;
273681090d4Sespie			}
274681090d4Sespie		}
275681090d4Sespie	}
276681090d4Sespie	if (keys %$bad > 0) {
277f1ddee08Sespie		my $bad2 = {};
278f1ddee08Sespie		for my $pkg (keys %$bad) {
279f1ddee08Sespie			if (!is_installed($pkg)) {
280f1ddee08Sespie				$bad2->{$pkg} = 1;
281f1ddee08Sespie			}
282f1ddee08Sespie		}
283f1ddee08Sespie		if (keys %$bad2 > 0) {
2846ba149ccSphessler			$state->errsay("#1 depends on non-existent #2",
285303a35c3Sespie			    $set->print, join(' ', sort keys %$bad2));
286f1ddee08Sespie			if (fix_bad_dependencies($state)) {
287f1ddee08Sespie				for my $pkg (keys %$bad2) {
288f1ddee08Sespie					delete $bad->{$pkg};
289f1ddee08Sespie				}
290f1ddee08Sespie			}
291f1ddee08Sespie		}
292f1ddee08Sespie	}
293*ab16e3ccSespie	# that's where I should check for alternates in bad
294f1ddee08Sespie	if (keys %$bad > 0) {
295fef35309Sespie		if (!$state->{do_automatic} || $state->verbose) {
296681090d4Sespie			$state->errsay("can't delete #1 without deleting #2",
297303a35c3Sespie			    $set->print, join(' ', sort keys %$bad));
29830df58edSespie		}
299fef35309Sespie		if (!$state->{do_automatic}) {
300f1ddee08Sespie			if (delete_dependencies($state)) {
301681090d4Sespie			    	my $l = create_locations($state, keys %$bad);
302716bc83bSespie				$state->tracker->todo(@$l);
303681090d4Sespie				return (@$l, $set);
304681090d4Sespie			}
305681090d4Sespie			$state->{bad}++;
306681090d4Sespie	    	}
307681090d4Sespie		$set->cleanup(OpenBSD::Handle::CANT_DELETE);
30846412527Sespie		$state->tracker->cant($set);
309681090d4Sespie		return ();
310681090d4Sespie	}
311681090d4Sespie	# XXX this si where we should detect loops
312681090d4Sespie	if (keys %$todo > 0) {
313681090d4Sespie		if ($set->{once}) {
314681090d4Sespie			for my $set2 (values %$todo) {
315681090d4Sespie				# XXX merge all ?
316681090d4Sespie				$set->add_older($set2->older);
317681090d4Sespie				$set2->{merged} = $set;
318681090d4Sespie				$set2->{finished} = 1;
319681090d4Sespie			}
320681090d4Sespie			delete $set->{once};
321681090d4Sespie			return ($set);
322681090d4Sespie		}
323681090d4Sespie		$set->{once} = 1;
324d2f781d7Sespie		$state->build_deptree($set, values %$todo);
325681090d4Sespie		return (values %$todo, $set);
326681090d4Sespie	}
327681090d4Sespie	for my $pkg ($set->older) {
328681090d4Sespie		$pkg->complete_old;
329ab0cb692Sespie		if (!defined $pkg->plist) {
330ab0cb692Sespie			$state->say("Corrupt set #1, run pkg_check",
331ab0cb692Sespie			    $set->print);
332ab0cb692Sespie			$set->cleanup(OpenBSD::Handle::CANT_DELETE);
333ab0cb692Sespie			$state->tracker->cant($set);
334ab0cb692Sespie			return ();
335ab0cb692Sespie		}
336a0a2ed7eSespie		if ($state->{do_automatic} &&
337a0a2ed7eSespie		    $pkg->plist->has('manual-installation')) {
33830df58edSespie			$state->say("Won't delete manually installed #1",
339303a35c3Sespie			    $set->print) if $state->verbose;
340681090d4Sespie			$set->cleanup(OpenBSD::Handle::CANT_DELETE);
34146412527Sespie			$state->tracker->cant($set);
342681090d4Sespie			return ();
343681090d4Sespie		}
344e954bb43Sespie		if (defined $pkg->plist->{tags}) {
345e954bb43Sespie			if (!$set->solver->solve_tags($state)) {
3467f38ef04Sespie				$set->cleanup(OpenBSD::Handle::CANT_DELETE);
347e954bb43Sespie				$state->tracker->cant($set);
348e954bb43Sespie				return ();
349e954bb43Sespie			}
350e954bb43Sespie		}
351e954bb43Sespie	}
352681090d4Sespie	really_remove($set, $state);
35346412527Sespie	$set->cleanup;
35446412527Sespie	$state->tracker->done($set);
355681090d4Sespie	return ();
356681090d4Sespie}
357681090d4Sespie
358039cbdaaSespiesub main($self, $state)
359a409537dSespie{
360bd849d19Sespie	if ($state->{exclude}) {
361bd849d19Sespie		my $names = {};
362bd849d19Sespie		for my $l (@{$state->{setlist}}) {
363bd849d19Sespie			for my $n ($l->older_names) {
364bd849d19Sespie				$names->{$n} = 1;
365bd849d19Sespie			}
366bd849d19Sespie		}
367bd849d19Sespie		$state->{setlist} = [];
368bd849d19Sespie		my $inst = $state->repo->installed;
369bd849d19Sespie		for my $l (@{$inst->locations_list}) {
370bd849d19Sespie			$self->add_location($state, $l) if !$names->{$l->name};
371bd849d19Sespie		}
372bd849d19Sespie	}
373fef35309Sespie	if ($state->{automatic}) {
374c487fac2Sespie		if (!defined $state->{setlist}) {
375fef35309Sespie			my $inst = $state->repo->installed;
376fef35309Sespie			for my $l (@{$inst->locations_list}) {
377fef35309Sespie				$self->add_location($state, $l);
378fef35309Sespie			}
3794809484aSespie		}
380fef35309Sespie		$state->{do_automatic} = 1;
381fef35309Sespie		$self->process_setlist($state);
3824809484aSespie	} else {
3834809484aSespie		$self->process_setlist($state);
384fef35309Sespie	}
385a409537dSespie}
386a409537dSespie
387039cbdaaSespiesub new_state($self, $cmd)
388a409537dSespie{
3897e83eca3Sespie	return OpenBSD::PkgDelete::State->new($cmd);
390a409537dSespie}
391a409537dSespie
392a409537dSespie1;
393