1a409537dSespie#!/usr/bin/perl
2a409537dSespie# ex:ts=8 sw=4:
3*45d818e9Sespie# $OpenBSD: PkgDelete.pm,v 1.31 2014/02/08 16:21:54 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
19a409537dSespieuse strict;
20a409537dSespieuse warnings;
21a409537dSespie
22a409537dSespieuse OpenBSD::AddDelete;
23a409537dSespie
24295c9761Sespie
25681090d4Sespiepackage OpenBSD::PkgDelete::Tracker;
26681090d4Sespie
27681090d4Sespiesub new
28681090d4Sespie{
29681090d4Sespie	my $class = shift;
30681090d4Sespie	bless {}, $class;
31681090d4Sespie}
32681090d4Sespie
33681090d4Sespiesub sets_todo
34681090d4Sespie{
35681090d4Sespie	my ($self, $offset) = @_;
36681090d4Sespie	return sprintf("%u/%u", (scalar keys %{$self->{done}})-$offset,
37681090d4Sespie		scalar keys %{$self->{total}});
38681090d4Sespie}
39681090d4Sespie
40681090d4Sespiesub handle_set
41681090d4Sespie{
42681090d4Sespie	my ($self, $set) = @_;
43681090d4Sespie	$self->{total}{$set} = 1;
44681090d4Sespie	if ($set->{finished}) {
45681090d4Sespie		$self->{done}{$set} = 1;
46681090d4Sespie	}
47681090d4Sespie}
48681090d4Sespie
49681090d4Sespiesub todo
50681090d4Sespie{
5146412527Sespie	my ($self, @list) = @_;
5246412527Sespie	for my $set (@list) {
53681090d4Sespie		for my $pkgname ($set->older_names) {
54681090d4Sespie			$self->{todo}{$pkgname} = $set;
55681090d4Sespie		}
56681090d4Sespie		$self->handle_set($set);
57681090d4Sespie	}
58681090d4Sespie}
59681090d4Sespie
60681090d4Sespie
61681090d4Sespiesub done
62681090d4Sespie{
63681090d4Sespie	my ($self, $set) = @_;
64681090d4Sespie	$set->{finished} = 1;
65681090d4Sespie	for my $pkgname ($set->older_names) {
66681090d4Sespie		delete $self->{todo}{$pkgname};
67681090d4Sespie	}
68681090d4Sespie	$self->handle_set($set);
69681090d4Sespie}
70681090d4Sespie
7146412527Sespiesub cant
7246412527Sespie{
7346412527Sespie	&done;
7446412527Sespie}
75681090d4Sespiesub find
76681090d4Sespie{
77681090d4Sespie	my ($self, $pkgname) = @_;
78681090d4Sespie	return $self->{todo}{$pkgname};
79681090d4Sespie}
80681090d4Sespie
81681090d4Sespie
82681090d4Sespie
83a409537dSespiepackage OpenBSD::PkgDelete::State;
84a409537dSespieour @ISA = qw(OpenBSD::AddDelete::State);
85a409537dSespie
86681090d4Sespiesub new
87681090d4Sespie{
88681090d4Sespie	my $class = shift;
89681090d4Sespie	my $self = $class->SUPER::new(@_);
90681090d4Sespie	$self->{tracker} = OpenBSD::PkgDelete::Tracker->new;
91681090d4Sespie	return $self;
92681090d4Sespie}
93681090d4Sespie
94681090d4Sespiesub tracker
95681090d4Sespie{
96681090d4Sespie	my $self = shift;
97681090d4Sespie	return $self->{tracker};
98681090d4Sespie}
99681090d4Sespie
1008a660df1Sespiesub handle_options
1018a660df1Sespie{
1028a660df1Sespie	my $state = shift;
103bd849d19Sespie	$state->SUPER::handle_options('X',
104661d6a5dSjmc	    '[-acimnqsvXx] [-B pkg-destdir] [-D name[=value]] pkg-name [...]');
1058a660df1Sespie
1068a660df1Sespie	my $base = $state->opt('B') // $ENV{'PKG_DESTDIR'} // '';
1078a660df1Sespie	if ($base ne '') {
1088a660df1Sespie		$base.='/' unless $base =~ m/\/$/o;
1098a660df1Sespie	}
1108a660df1Sespie	$ENV{'PKG_DESTDIR'} = $base;
1118a660df1Sespie
1128a660df1Sespie	$state->{destdir} = $base;
1138a660df1Sespie	if ($base eq '') {
1148a660df1Sespie	    $state->{destdirname} = '';
1158a660df1Sespie	} else {
1168a660df1Sespie	    $state->{destdirname} = '${PKG_DESTDIR}';
1178a660df1Sespie	}
118bd849d19Sespie	$state->{exclude} = $state->opt('X');
1198a660df1Sespie}
1208a660df1Sespie
121681090d4Sespiesub stem2location
122681090d4Sespie{
123681090d4Sespie	my ($self, $locator, $name, $state) = @_;
124681090d4Sespie	require OpenBSD::Search;
125681090d4Sespie	my $l = $locator->match_locations(OpenBSD::Search::Stem->new($name));
126681090d4Sespie	if (@$l > 1 && !$state->defines('allversions')) {
127681090d4Sespie		$l = OpenBSD::Search::FilterLocation->keep_most_recent->filter_locations($l);
128681090d4Sespie	}
129681090d4Sespie	return $state->choose_location($name, $l);
130a409537dSespie}
131a409537dSespie
132303a35c3Sespiesub deleteset
133303a35c3Sespie{
134303a35c3Sespie	my $self = shift;
135303a35c3Sespie	require OpenBSD::UpdateSet;
136303a35c3Sespie
137303a35c3Sespie	return OpenBSD::DeleteSet->new($self);
138303a35c3Sespie}
139303a35c3Sespie
140303a35c3Sespiesub deleteset_from_location
141303a35c3Sespie{
142303a35c3Sespie	my ($self, $location) = @_;
143303a35c3Sespie	return $self->deleteset->add_older(OpenBSD::Handle->from_location($location));
144303a35c3Sespie}
145303a35c3Sespie
146d2f781d7Sespiepackage OpenBSD::DeleteSet;
147d2f781d7Sespiesub setup_header
148d2f781d7Sespie{
149d2f781d7Sespie	my ($set, $state, $handle) = @_;
150d2f781d7Sespie	my $header = $state->deptree_header($set);
151d2f781d7Sespie	if (defined $handle) {
152d2f781d7Sespie		$header .= $handle->pkgname;
153d2f781d7Sespie	} else {
154d2f781d7Sespie		$header .= $set->print;
155d2f781d7Sespie	}
156d2f781d7Sespie	if (!$state->progress->set_header($header)) {
157d2f781d7Sespie		return unless $state->verbose;
158d2f781d7Sespie		$header = "Deleting $header";
159d2f781d7Sespie		if (defined $state->{lastheader} &&
160d2f781d7Sespie		    $header eq $state->{lastheader}) {
161d2f781d7Sespie			return;
162d2f781d7Sespie		}
163d2f781d7Sespie		$state->{lastheader} = $header;
164d2f781d7Sespie		$state->print("#1", $header);
165d2f781d7Sespie		$state->print("(pretending) ") if $state->{not};
166d2f781d7Sespie		if ($state->{do_faked}) {
167d2f781d7Sespie			$state->print(" under #1", $state->{destdir});
168d2f781d7Sespie		}
169d2f781d7Sespie		$state->print("\n");
170d2f781d7Sespie	}
171d2f781d7Sespie}
172d2f781d7Sespie
173a409537dSespiepackage OpenBSD::PkgDelete;
174a409537dSespieour @ISA = qw(OpenBSD::AddDelete);
175a409537dSespie
176a409537dSespieuse OpenBSD::PackingList;
177a409537dSespieuse OpenBSD::RequiredBy;
178a409537dSespieuse OpenBSD::Delete;
179a409537dSespieuse OpenBSD::PackageInfo;
180a409537dSespieuse OpenBSD::UpdateSet;
181681090d4Sespieuse OpenBSD::Handle;
182a409537dSespie
183a409537dSespie
184681090d4Sespiesub add_location
185681090d4Sespie{
186681090d4Sespie	my ($self, $state, $l) = @_;
187681090d4Sespie	push(@{$state->{setlist}},
188303a35c3Sespie	    $state->deleteset_from_location($l));
189681090d4Sespie}
190681090d4Sespie
191681090d4Sespiesub create_locations
192681090d4Sespie{
193681090d4Sespie	my ($state, @l) = @_;
194681090d4Sespie	my $inst = $state->repo->installed;
195681090d4Sespie	my $result = [];
196681090d4Sespie	for my $name (@l) {
197681090d4Sespie		my $l = $inst->find($name, $state->{arch});
198681090d4Sespie		if (!defined $l) {
199681090d4Sespie			$state->errsay("Can't find #1 in installed packages",
200681090d4Sespie			    $name);
201681090d4Sespie			$state->{bad}++;
202681090d4Sespie		} else {
203303a35c3Sespie			push(@$result, $state->deleteset_from_location($l));
204681090d4Sespie		}
205681090d4Sespie	}
206681090d4Sespie	return $result;
207681090d4Sespie}
208681090d4Sespie
209a409537dSespiesub process_parameters
210a409537dSespie{
211a409537dSespie	my ($self, $state) = @_;
2127096cf21Sespie
213681090d4Sespie	my $inst = $state->repo->installed;
2147096cf21Sespie
215fef35309Sespie	if (@ARGV == 0) {
21653a284c3Sespie		if (!($state->{automatic} || $state->{exclude})) {
217fef35309Sespie			$state->fatal("No packages to delete");
218a409537dSespie		}
219a409537dSespie	} else {
220681090d4Sespie		for my $pkgname (@ARGV) {
221681090d4Sespie			my $l;
222681090d4Sespie
223681090d4Sespie			if (OpenBSD::PackageName::is_stem($pkgname)) {
224fef35309Sespie				$l = $state->stem2location($inst, $pkgname,
225fef35309Sespie				    $state);
226681090d4Sespie			} else {
227db099e94Sespie				$l = $inst->find($pkgname);
228681090d4Sespie			}
229681090d4Sespie			if (!defined $l) {
230295c9761Sespie				unless ($state->{exclude}) {
231*45d818e9Sespie					$state->say("Problem finding #1",
232*45d818e9Sespie					    $pkgname);
233a409537dSespie					$state->{bad}++;
234295c9761Sespie				}
235681090d4Sespie			} else {
236681090d4Sespie				$self->add_location($state, $l);
237a409537dSespie			}
238a409537dSespie		}
239681090d4Sespie	}
240a409537dSespie}
241a409537dSespie
242a409537dSespiesub finish_display
243a409537dSespie{
244a409537dSespie}
245a409537dSespie
246681090d4Sespiesub really_remove
247681090d4Sespie{
248681090d4Sespie	my ($set, $state) = @_;
249681090d4Sespie	if ($state->{not}) {
250681090d4Sespie		$state->status->what("Pretending to delete");
251681090d4Sespie	} else {
252681090d4Sespie		$state->status->what("Deleting");
253681090d4Sespie	}
254d2f781d7Sespie	$set->setup_header($state);
255681090d4Sespie	for my $pkgname ($set->older_names) {
256d2f781d7Sespie		$set->setup_header($state, $set->{older}{$pkgname});
257681090d4Sespie		$state->log->set_context('-'.$pkgname);
258681090d4Sespie		OpenBSD::Delete::delete_package($pkgname, $state);
259681090d4Sespie	}
260681090d4Sespie	$state->progress->next($state->ntogo);
261de512720Sespie	$state->syslog("Removed #1", $set->print);
262681090d4Sespie}
263681090d4Sespie
264f1ddee08Sespiesub delete_dependencies
265f1ddee08Sespie{
266f1ddee08Sespie	my $state = shift;
267f1ddee08Sespie
268f1ddee08Sespie	if ($state->defines("dependencies")) {
269f1ddee08Sespie		return 1;
270f1ddee08Sespie	}
271f1ddee08Sespie	if ($state->{interactive}) {
272f1ddee08Sespie		return $state->confirm("Delete them as well", 0);
273f1ddee08Sespie	}
274f1ddee08Sespie	return 0;
275f1ddee08Sespie}
276f1ddee08Sespie
277f1ddee08Sespiesub fix_bad_dependencies
278f1ddee08Sespie{
279f1ddee08Sespie	my $state = shift;
280f1ddee08Sespie
281f1ddee08Sespie	if ($state->defines("baddepend")) {
282f1ddee08Sespie		return 1;
283f1ddee08Sespie	}
284f1ddee08Sespie	if ($state->{interactive}) {
285f1ddee08Sespie		return $state->confirm("Delete anyways", 0);
286f1ddee08Sespie	}
287f1ddee08Sespie	return 0;
288f1ddee08Sespie}
289f1ddee08Sespie
29046412527Sespiesub process_set
291681090d4Sespie{
29246412527Sespie	my ($self, $set, $state) = @_;
293681090d4Sespie
294681090d4Sespie	my $todo = {};
295681090d4Sespie	my $bad = {};
296681090d4Sespie    	for my $pkgname ($set->older_names) {
297681090d4Sespie		unless (is_installed($pkgname)) {
298681090d4Sespie			$state->errsay("#1 was not installed", $pkgname);
299681090d4Sespie			$set->{finished} = 1;
300681090d4Sespie			$set->cleanup(OpenBSD::Handle::NOT_FOUND);
301681090d4Sespie			$state->{bad}++;
302681090d4Sespie			return ();
303681090d4Sespie		}
304681090d4Sespie		my $r = OpenBSD::RequiredBy->new($pkgname);
305681090d4Sespie		for my $pkg ($r->list) {
306681090d4Sespie			next if $set->{older}->{$pkg};
307681090d4Sespie			my $f = $state->tracker->find($pkg);
308681090d4Sespie			if (defined $f) {
309681090d4Sespie				$todo->{$pkg} = $f;
310681090d4Sespie			} else {
311681090d4Sespie				$bad->{$pkg} = 1;
312681090d4Sespie			}
313681090d4Sespie		}
314681090d4Sespie	}
315681090d4Sespie	if (keys %$bad > 0) {
316f1ddee08Sespie		my $bad2 = {};
317f1ddee08Sespie		for my $pkg (keys %$bad) {
318f1ddee08Sespie			if (!is_installed($pkg)) {
319f1ddee08Sespie				$bad2->{$pkg} = 1;
320f1ddee08Sespie			}
321f1ddee08Sespie		}
322f1ddee08Sespie		if (keys %$bad2 > 0) {
323f1ddee08Sespie			$state->errsay("#1 depends on non-existant #2",
324303a35c3Sespie			    $set->print, join(' ', sort keys %$bad2));
325f1ddee08Sespie			if (fix_bad_dependencies($state)) {
326f1ddee08Sespie				for my $pkg (keys %$bad2) {
327f1ddee08Sespie					delete $bad->{$pkg};
328f1ddee08Sespie				}
329f1ddee08Sespie			}
330f1ddee08Sespie		}
331f1ddee08Sespie	}
332f1ddee08Sespie	if (keys %$bad > 0) {
333fef35309Sespie		if (!$state->{do_automatic} || $state->verbose) {
334681090d4Sespie			$state->errsay("can't delete #1 without deleting #2",
335303a35c3Sespie			    $set->print, join(' ', sort keys %$bad));
33630df58edSespie		}
337fef35309Sespie		if (!$state->{do_automatic}) {
338f1ddee08Sespie			if (delete_dependencies($state)) {
339681090d4Sespie			    	my $l = create_locations($state, keys %$bad);
340716bc83bSespie				$state->tracker->todo(@$l);
341681090d4Sespie				return (@$l, $set);
342681090d4Sespie			}
343681090d4Sespie			$state->{bad}++;
344681090d4Sespie	    	}
345681090d4Sespie		$set->cleanup(OpenBSD::Handle::CANT_DELETE);
34646412527Sespie		$state->tracker->cant($set);
347681090d4Sespie		return ();
348681090d4Sespie	}
349681090d4Sespie	# XXX this si where we should detect loops
350681090d4Sespie	if (keys %$todo > 0) {
351681090d4Sespie		if ($set->{once}) {
352681090d4Sespie			for my $set2 (values %$todo) {
353681090d4Sespie				# XXX merge all ?
354681090d4Sespie				$set->add_older($set2->older);
355681090d4Sespie				$set2->{merged} = $set;
356681090d4Sespie				$set2->{finished} = 1;
357681090d4Sespie			}
358681090d4Sespie			delete $set->{once};
359681090d4Sespie			return ($set);
360681090d4Sespie		}
361681090d4Sespie		$set->{once} = 1;
362d2f781d7Sespie		$state->build_deptree($set, values %$todo);
363681090d4Sespie		return (values %$todo, $set);
364681090d4Sespie	}
365fef35309Sespie	if ($state->{do_automatic}) {
366681090d4Sespie		for my $pkg  ($set->older) {
367681090d4Sespie			$pkg->complete_old;
368681090d4Sespie			if ($pkg->plist->has('manual-installation')) {
36930df58edSespie				$state->say("Won't delete manually installed #1",
370303a35c3Sespie				    $set->print) if $state->verbose;
371681090d4Sespie				$set->cleanup(OpenBSD::Handle::CANT_DELETE);
37246412527Sespie				$state->tracker->cant($set);
373681090d4Sespie				return ();
374681090d4Sespie			}
375681090d4Sespie		}
376681090d4Sespie	}
377681090d4Sespie	really_remove($set, $state);
37846412527Sespie	$set->cleanup;
37946412527Sespie	$state->tracker->done($set);
380681090d4Sespie	return ();
381681090d4Sespie}
382681090d4Sespie
383a409537dSespiesub main
384a409537dSespie{
385a409537dSespie	my ($self, $state) = @_;
386a409537dSespie
387bd849d19Sespie	if ($state->{exclude}) {
388bd849d19Sespie		my $names = {};
389bd849d19Sespie		for my $l (@{$state->{setlist}}) {
390bd849d19Sespie			for my $n ($l->older_names) {
391bd849d19Sespie				$names->{$n} = 1;
392bd849d19Sespie			}
393bd849d19Sespie		}
394bd849d19Sespie		$state->{setlist} = [];
395bd849d19Sespie		my $inst = $state->repo->installed;
396bd849d19Sespie		for my $l (@{$inst->locations_list}) {
397bd849d19Sespie			$self->add_location($state, $l) if !$names->{$l->name};
398bd849d19Sespie		}
399bd849d19Sespie	}
400fef35309Sespie	if ($state->{automatic}) {
401c487fac2Sespie		if (!defined $state->{setlist}) {
402fef35309Sespie			my $inst = $state->repo->installed;
403fef35309Sespie			for my $l (@{$inst->locations_list}) {
404fef35309Sespie				$self->add_location($state, $l);
405fef35309Sespie			}
4064809484aSespie		}
407fef35309Sespie		$state->{do_automatic} = 1;
408fef35309Sespie		$self->process_setlist($state);
4094809484aSespie	} else {
4104809484aSespie		$self->process_setlist($state);
411fef35309Sespie	}
412a409537dSespie}
413a409537dSespie
414a409537dSespiesub new_state
415a409537dSespie{
4167e83eca3Sespie	my ($self, $cmd) = @_;
4177e83eca3Sespie	return OpenBSD::PkgDelete::State->new($cmd);
418a409537dSespie}
419a409537dSespie
420a409537dSespie1;
421