xref: /openbsd/usr.sbin/pkg_add/OpenBSD/Delete.pm (revision d6425245)
1# ex:ts=8 sw=4:
2# $OpenBSD: Delete.pm,v 1.169 2023/10/11 13:54:43 espie Exp $
3#
4# Copyright (c) 2003-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# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
18use v5.36;
19
20package OpenBSD::Delete;
21use OpenBSD::Error;
22use OpenBSD::PackageInfo;
23use OpenBSD::RequiredBy;
24use OpenBSD::Paths;
25use File::Basename;
26
27sub keep_old_files($state, $plist)
28{
29	my $p = OpenBSD::PackingList->new;
30	my $borked = borked_package($plist->pkgname);
31	$p->set_infodir(installed_info($borked));
32	mkdir($p->infodir);
33
34	$plist->copy_old_stuff($p, $state);
35	$p->set_pkgname($borked);
36	$p->to_installation;
37	return $borked;
38}
39
40sub manpages_unindex($state)
41{
42	return unless defined $state->{rmman};
43	my $destdir = $state->{destdir};
44
45	while (my ($k, $v) = each %{$state->{rmman}}) {
46		my @l = map { "$destdir$k/$_" } @$v;
47		if ($state->{not}) {
48			$state->say("Removing manpages in #1: #2",
49			    $destdir.$k, join(' ', @l)) if $state->verbose;
50		} else {
51			$state->run_makewhatis(['-u', $destdir.$k], \@l);
52		}
53	}
54	delete $state->{rmman};
55}
56
57sub validate_plist($plist, $state)
58{
59	$plist->prepare_for_deletion($state, $plist->pkgname);
60}
61
62sub remove_packing_info($plist, $state)
63{
64	my $dir = $plist->infodir;
65
66	for my $fname (info_names()) {
67		unlink($dir.$fname);
68	}
69	OpenBSD::RequiredBy->forget($dir);
70	OpenBSD::Requiring->forget($dir);
71	rmdir($dir) or
72	    $state->fatal("can't finish removing directory #1: #2", $dir, $!);
73}
74
75sub delete_handle($handle, $state)
76{
77	my $pkgname = $handle->pkgname;
78	my $plist = $handle->plist;
79	if ($plist->has('firmware') && !$state->defines('FW_UPDATE')) {
80		if ($state->is_interactive) {
81			if (!$state->confirm_defaults_to_no(
82			    "\nDelete firmware #1", $pkgname)) {
83				$state->errsay("NOT deleting #1", $pkgname);
84				return;
85			}
86		} else {
87			$state->errsay("NOT deleting #1: use fw_update -d",
88			    $pkgname);
89			return;
90		}
91	}
92
93	$state->{problems} = 0;
94	validate_plist($plist, $state);
95	$state->fatal("can't recover from deinstalling #1", $pkgname)
96	    if $state->{problems};
97	$state->vstat->synchronize;
98
99	delete_plist($plist, $state);
100}
101
102sub unregister_dependencies($plist, $state)
103{
104	my $pkgname = $plist->pkgname;
105	my $l = OpenBSD::Requiring->new($pkgname);
106
107	for my $name ($l->list) {
108		$state->say("remove dependency of #1 on #2", $pkgname, $name)
109		    if $state->verbose >= 3;
110		local $@;
111		try {
112			OpenBSD::RequiredBy->new($name)->delete($pkgname);
113		} catch {
114			$state->errsay($_);
115		};
116	}
117	$l->erase;
118}
119
120sub delete_plist($plist, $state)
121{
122	my $pkgname = $plist->pkgname;
123	$state->{pkgname} = $pkgname;
124	if (!$state->{regression}{stub} || $pkgname =~ /^quirks\-/) {
125		if (!$state->{size_only}) {
126			$plist->register_manpage($state, 'rmman');
127			manpages_unindex($state);
128			$state->progress->visit_with_size($plist, 'delete');
129		}
130	}
131
132	unregister_dependencies($plist, $state);
133	return if $state->{not};
134	if ($state->{baddelete}) {
135	    my $borked = keep_old_files($state, $plist);
136	    $state->log("Files kept as #1 package", $borked);
137	    delete $state->{baddelete};
138	}
139
140
141	remove_packing_info($plist, $state);
142	delete_installed($pkgname);
143}
144
145package OpenBSD::PackingElement;
146
147sub rename_file_to_temp($self, $state)
148{
149	require OpenBSD::Temp;
150
151	my $n = $self->realname($state);
152
153	my (undef, $j) = OpenBSD::Temp::permanent_file(undef, $n);
154	if (!defined $j) {
155		$state->errsay(OpenBSD::Temp->last_error);
156		return;
157	}
158	if (rename($n, $j)) {
159		$state->say("Renaming old file #1 to #2", $n, $j);
160		if ($self->name !~ m/^\//o && $self->cwd ne '.') {
161			my $c = $self->cwd;
162			$j =~ s|^\Q$c\E/||;
163		}
164		$self->set_name($j);
165	} else {
166		$state->errsay("Bad rename #1 to #2: #3", $n, $j, $!);
167	}
168}
169
170# $self->prepare_for_deletion($state, $pkgname)
171sub prepare_for_deletion($, $, $)
172{
173}
174
175# $self->delete($state)
176sub delete($, $)
177{
178}
179
180# $self->record_shared($recorder, $pkgname)
181sub record_shared($, $, $)
182{
183}
184
185sub copy_old_stuff($self, $plist, $state)
186{
187}
188
189package OpenBSD::PackingElement::Cwd;
190
191sub copy_old_stuff($self, $plist, $state)
192{
193	$self->add_object($plist);
194}
195
196package OpenBSD::PackingElement::FileObject;
197use File::Basename;
198
199sub mark_directory($self, $state, $dir)
200{
201	$state->{dirs_okay}{$dir} = 1;
202	my $d2 = dirname($dir);
203	if ($d2 ne $dir) {
204		$self->mark_directory($state, $d2);
205	}
206}
207
208sub mark_dir($self, $state)
209{
210	$self->mark_directory($state, dirname($self->fullname));
211}
212
213sub do_not_delete($self, $state)
214{
215	my $realname = $self->realname($state);
216	$state->{baddelete} = 1;
217	$self->{stillaround} = 1;
218
219	delete $self->{symlink};
220	delete $self->{link};
221	my $algo = $self->{d};
222	delete $self->{d};
223
224	if (-l $realname) {
225		$self->{symlink} = readlink $realname;
226	} elsif (-f _) {
227		$self->{d} = $self->compute_digest($realname, $algo);
228	} elsif (-d _) {
229		# what should we do ?
230	}
231}
232
233
234package OpenBSD::PackingElement::DirlikeObject;
235sub mark_dir($self, $state)
236{
237	$self->mark_directory($state, $self->fullname);
238}
239
240package OpenBSD::PackingElement::RcScript;
241# XXX we should check stuff more thoroughly
242
243sub delete($self, $state)
244{
245	$state->{delete_rcscripts}{$self->fullname} = 1;
246	$self->SUPER::delete($state);
247}
248
249package OpenBSD::PackingElement::NewUser;
250sub delete($self, $state)
251{
252	if ($state->verbose >= 2) {
253		$state->say("rmuser: #1", $self->name);
254	}
255
256	$self->record_shared($state->{recorder}, $state->{pkgname});
257}
258
259sub record_shared($self, $recorder, $pkgname)
260{
261	$recorder->{users}{$self->name} = $pkgname;
262}
263
264package OpenBSD::PackingElement::NewGroup;
265sub delete($self, $state)
266{
267	if ($state->verbose >= 2) {
268		$state->say("rmgroup: #1", $self->name);
269	}
270
271	$self->record_shared($state->{recorder}, $state->{pkgname});
272}
273
274sub record_shared($self, $recorder, $pkgname)
275{
276	$recorder->{groups}{$self->name} = $pkgname;
277}
278
279package OpenBSD::PackingElement::DirBase;
280sub prepare_for_deletion($self, $state, $pkgname)
281{
282	$state->vstat->remove_directory(
283	    $self->retrieve_fullname($state, $pkgname), $self);
284}
285
286sub delete($self, $state)
287{
288	if ($state->verbose >= 5) {
289		$state->say("rmdir: #1", $self->fullname);
290	}
291
292	$self->record_shared($state->{recorder}, $state->{pkgname});
293}
294
295sub record_shared($self, $recorder, $pkgname)
296{
297	# enough for the entry to exist, we only record interesting
298	# entries more thoroughly
299	$recorder->{dirs}{$self->fullname} //= [];
300}
301
302package OpenBSD::PackingElement::Mandir;
303sub record_shared($self, $recorder, $pkgname)
304{
305	$self->{pkgname} = $pkgname;
306	push(@{$recorder->{dirs}{$self->fullname}} , $self);
307}
308
309package OpenBSD::PackingElement::Fontdir;
310sub record_shared($self, $recorder, $pkgname)
311{
312	$self->{pkgname} = $pkgname;
313	push(@{$recorder->{dirs}{$self->fullname}} , $self);
314	$recorder->{fonts_todo}{$self->fullname} = 1;
315}
316
317package OpenBSD::PackingElement::Infodir;
318sub record_shared	# forwarder
319{
320	&OpenBSD::PackingElement::Mandir::record_shared;
321}
322
323package OpenBSD::PackingElement::Unexec;
324sub delete($self, $state)
325{
326	if ($self->should_run($state)) {
327		$self->run($state);
328	}
329}
330
331sub should_run($, $) { 1 }
332
333package OpenBSD::PackingElement::UnexecDelete;
334sub should_run($self, $state)
335{
336	return !$state->replacing;
337}
338
339package OpenBSD::PackingElement::UnexecUpdate;
340sub should_run($self, $state)
341{
342	return $state->replacing;
343}
344
345package OpenBSD::PackingElement::DefineTag::Atend;
346sub delete($self, $state)
347{
348	if (!$state->replacing) {
349		$state->{tags}{deleted}{$self->name} = 1;
350	}
351}
352
353
354package OpenBSD::PackingElement::Tag;
355sub delete($self, $state)
356{
357	for my $d (@{$self->{definition_list}}) {
358		$d->add_tag($self, "delete", $state);
359	}
360}
361
362package OpenBSD::PackingElement::FileBase;
363use OpenBSD::Error;
364
365sub prepare_for_deletion($self, $state, $pkgname)
366{
367	my $fname = $self->retrieve_fullname($state, $pkgname);
368	my $s;
369	my $size = $self->{tied} ? 0 : $self->retrieve_size;
370	if ($state->{delete_first}) {
371		$s = $state->vstat->remove_first($fname, $size);
372	} else {
373		$s = $state->vstat->remove($fname, $size);
374	}
375	return unless defined $s;
376	if ($s->ro) {
377		$s->report_ro($state, $fname);
378	}
379}
380
381sub is_intact($self, $state, $realname)
382{
383	return 1 if defined($self->{link}) or $self->{nochecksum};
384	if (!defined $self->{d}) {
385		if ($self->fullname eq $realname) {
386			$state->say("NOT deleting #1 (no checksum)", $realname);
387		} else {
388			$state->say("Not deleting #1 (no checksum for #2",
389			    $realname, $self->fullname);
390		}
391		$state->log("Couldn't delete #1 (no checksum)", $realname);
392		return 0;
393	}
394	return 1 unless $state->defines('checksum');
395	my $d = $self->compute_digest($realname, $self->{d});
396	return 1 if $d->equals($self->{d});
397	if ($self->fullname eq $realname) {
398		$state->say("NOT deleting #1 (bad checksum)", $realname);
399	} else {
400		$state->say("Not deleting #1 (bad checksum for #2)",
401		    $realname, $self->fullname);
402	}
403	$state->log("Couldn't delete #1 (bad checksum)", $realname);
404	return 0;
405}
406
407sub delete($self, $state)
408{
409	my $realname = $self->realname($state);
410	return if defined $state->{current_set}{dont_delete}{$realname};
411
412	if (defined $self->{symlink}) {
413		if (-l $realname) {
414			my $contents = readlink $realname;
415			if ($contents ne $self->{symlink}) {
416				$state->say("Symlink does not match: #1 (#2 vs. #3)",
417				    $realname, $contents, $self->{symlink});
418				$self->do_not_delete($state);
419				return;
420			}
421		} else  {
422			if (-e $realname) {
423				$state->say("Bogus symlink: #1", $realname);
424				$self->do_not_delete($state);
425			} else {
426				$state->say("Can't delete missing symlink: #1",
427				    $realname);
428			}
429			return;
430		}
431	} else {
432		if (-l $realname) {
433				$state->say("Unexpected symlink: #1", $realname);
434				$self->do_not_delete($state);
435		} else {
436			if (!-f $realname) {
437				$state->say("File #1 does not exist", $realname);
438				return;
439			}
440			if (!$self->is_intact($state, $realname)) {
441				$self->do_not_delete($state);
442				return;
443			}
444		}
445	}
446	if ($state->verbose >= 5) {
447		$state->say("deleting: #1", $realname);
448	}
449	return if $state->{not};
450	if ($state->{delete_first} && $self->{tied}) {
451		push(@{$state->{delayed}}, $realname);
452	} else {
453		if (!unlink $realname) {
454			$state->errsay("Problem deleting #1: #2", $realname,
455			    $!);
456			$state->log("deleting #1 failed: #2", $realname, $!);
457		}
458	}
459}
460
461sub copy_old_stuff($self, $plist, $state)
462{
463	if (defined $self->{stillaround}) {
464		delete $self->{stillaround};
465		if ($state->replacing) {
466			$self->rename_file_to_temp($state);
467		}
468		$self->add_object($plist);
469	}
470}
471
472package OpenBSD::PackingElement::SpecialFile;
473use OpenBSD::PackageInfo;
474
475sub copy_old_stuff($, $, $)
476{
477}
478
479package OpenBSD::PackingElement::Meta;
480sub copy_old_stuff($self, $plist, $state)
481{
482	$self->add_object($plist);
483}
484
485package OpenBSD::PackingElement::DigitalSignature;
486sub copy_old_stuff($, $, $)
487{
488}
489
490package OpenBSD::PackingElement::FDESC;
491sub copy_old_stuff($self, $plist, $state)
492{
493	require File::Copy;
494
495	File::Copy::copy($self->fullname, $plist->infodir);
496	$self->add_object($plist);
497}
498
499package OpenBSD::PackingElement::Sample;
500use OpenBSD::Error;
501use File::Basename;
502
503sub delete($self, $state)
504{
505	my $realname = $self->realname($state);
506
507	my $orig = $self->{copyfrom};
508	if (!defined $orig) {
509		$state->fatal("\@sample element does not reference a valid file");
510	}
511	my $action = $state->replacing ? "check" : "remove";
512	my $origname = $orig->realname($state);
513	if (! -e $realname) {
514		$state->log("File #1 does not exist", $realname);
515		return;
516	}
517	if (! -f $realname) {
518		$state->log("File #1 is not a file", $realname);
519		return;
520	}
521
522	if (!defined $orig->{d}) {
523		$state->log("Couldn't delete #1 (no checksum)", $realname);
524		return;
525	}
526
527	if ($state->{quick} && $state->{quick} >= 2) {
528		unless ($state->{extra}) {
529			$self->mark_dir($state);
530			$state->log("You should also #1 #2", $action, $realname );
531			return;
532		}
533	} else {
534		my $d = $self->compute_digest($realname, $orig->{d});
535		if ($d->equals($orig->{d})) {
536			$state->say("File #1 identical to sample", $realname) if $state->verbose >= 2;
537		} else {
538			unless ($state->{extra}) {
539				$self->mark_dir($state);
540				$state->log("You should also #1 #2 (which was modified)", $action, $realname);
541				return;
542			}
543		}
544	}
545	$state->say("deleting #1", $realname) if $state->verbose >= 2;
546	return if $state->{not};
547	if (!unlink $realname) {
548		$state->errsay("Problem deleting #1: #2", $realname, $!);
549		$state->log("deleting #1 failed: #2", $realname, $!);
550	}
551}
552
553
554package OpenBSD::PackingElement::InfoFile;
555use File::Basename;
556use OpenBSD::Error;
557sub delete($self, $state)
558{
559	unless ($state->{not}) {
560	    my $fullname = $state->{destdir}.$self->fullname;
561	    $state->vsystem(OpenBSD::Paths->install_info,
562		"--delete", "--info-dir=".dirname($fullname), '--', $fullname);
563	}
564	$self->SUPER::delete($state);
565}
566
567package OpenBSD::PackingElement::Shell;
568sub delete($self, $state)
569{
570	unless ($state->{not}) {
571		my $destdir = $state->{destdir};
572		my $fullname = $self->fullname;
573		my @l=();
574		if (open(my $shells, '<', $destdir.OpenBSD::Paths->shells)) {
575			while(<$shells>) {
576				push(@l, $_);
577				s/^\#.*//o;
578				if ($_ =~ m/^\Q$fullname\E\s*$/) {
579					pop(@l);
580				}
581			}
582			close($shells);
583			open(my $shells2, '>', $destdir.OpenBSD::Paths->shells);
584			print $shells2 @l;
585			close $shells2;
586			$state->say("Shell #1 removed from #2",
587			    $fullname, $destdir.OpenBSD::Paths->shells)
588			    	if $state->verbose;
589		}
590	}
591	$self->SUPER::delete($state);
592}
593
594package OpenBSD::PackingElement::Extra;
595use File::Basename;
596
597sub delete($self, $state)
598{
599	return if defined $state->{current_set}{known_extra}{$self->fullname};
600	my $realname = $self->realname($state);
601	if ($state->verbose >= 2 && $state->{extra}) {
602		$state->say("deleting extra file: #1", $realname);
603	}
604	return if $state->{not};
605	return unless -e $realname or -l $realname;
606	if ($state->{extra}) {
607		unlink($realname) or
608		    $state->say("problem deleting extra file #1: #2", $realname, $!);
609	} else {
610		$state->log("You should also remove #1", $realname);
611		$self->mark_dir($state);
612	}
613}
614
615
616package OpenBSD::PackingElement::Extradir;
617sub delete($self, $state)
618{
619	return unless $state->{extra};
620	return if defined $state->{current_set}{known_extra}{$self->fullname};
621	my $realname = $self->realname($state);
622	if ($state->{extra}) {
623		$self->SUPER::delete($state);
624	} else {
625		$state->log("You should also remove the directory #1", $realname);
626		$self->mark_dir($state);
627	}
628}
629
630package OpenBSD::PackingElement::ExtraUnexec;
631
632sub delete($self, $state)
633{
634	if ($state->{extra}) {
635		$self->run($state);
636	} else {
637		$state->log("You should also run #1", $self->{expanded});
638	}
639}
640
641package OpenBSD::PackingElement::Lib;
642sub delete($self, $state)
643{
644	$self->SUPER::delete($state);
645	$self->mark_ldconfig_directory($state);
646}
647
648package OpenBSD::PackingElement::Depend;
649sub copy_old_stuff($self, $plist, $state)
650{
651	OpenBSD::PackingElement::Comment->add($plist,
652	    "\@".$self->keyword." ".$self->stringize);
653}
654
655package OpenBSD::PackingElement::FDISPLAY;
656sub delete($self, $state)
657{
658	$state->{current_set}{known_displays}{$self->{d}->key} = 1;
659	$self->SUPER::delete($state);
660}
661
662package OpenBSD::PackingElement::FUNDISPLAY;
663sub delete($self, $state)
664{
665	my $d = $self->{d};
666	if (!$state->{current_set}{known_displays}{$self->{d}->key}) {
667		$self->prepare($state);
668	}
669	$self->SUPER::delete($state);
670}
671
672package OpenBSD::PackingElement::Mandir;
673sub delete($self, $state)
674{
675	$state->{current_set}{known_mandirs}{$self->fullname} = 1;
676	$self->SUPER::delete($state);
677}
678
6791;
680