xref: /openbsd/usr.sbin/pkg_add/OpenBSD/Add.pm (revision e5dd7070)
1# ex:ts=8 sw=4:
2# $OpenBSD: Add.pm,v 1.184 2020/05/17 14:34:47 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 strict;
19use warnings;
20
21package OpenBSD::Add;
22use OpenBSD::Error;
23use OpenBSD::PackageInfo;
24use OpenBSD::ArcCheck;
25use OpenBSD::Paths;
26use File::Copy;
27
28sub manpages_index
29{
30	my ($state) = @_;
31	return unless defined $state->{addman};
32	my $destdir = $state->{destdir};
33
34	# fudge verbose for API differences
35	while (my ($k, $v) = each %{$state->{addman}}) {
36		my @l = map { "$destdir$k/$_" } @$v;
37		if ($state->{not}) {
38			$state->say("Merging manpages in #1: #2",
39			    $destdir.$k, join(' ', @l)) if $state->verbose;
40		} else {
41			$state->run_makewhatis(['-d', $destdir.$k], \@l);
42		}
43	}
44	delete $state->{addman};
45}
46
47sub register_installation
48{
49	my ($plist, $state) = @_;
50	if ($state->{not}) {
51		$plist->to_cache;
52	} else {
53		my $dest = installed_info($plist->pkgname);
54		mkdir($dest);
55		$plist->copy_info($dest, $state);
56		$plist->set_infodir($dest);
57		$plist->to_installation;
58	}
59}
60
61sub validate_plist
62{
63	my ($plist, $state, $set) = @_;
64
65	$plist->prepare_for_addition($state, $plist->pkgname, $set);
66}
67
68sub record_partial_installation
69{
70	my ($plist, $state, $h) = @_;
71
72	use OpenBSD::PackingElement;
73
74	my $n = $plist->make_shallow_copy($h);
75	my $borked = borked_package($plist->pkgname);
76	$n->set_pkgname($borked);
77
78	# last file may have not copied correctly
79	my $last = $n->{state}->{lastfile};
80	if (defined $last && defined($last->{d})) {
81
82		my $old = $last->{d};
83		my $lastname = $last->realname($state);
84		if (-f $lastname) {
85			$last->{d} = $last->compute_digest($lastname, $old);
86			if (!$old->equals($last->{d})) {
87				$state->say("Adjusting #1 for #2 from #3 to #4",
88				    $old->keyword, $lastname, $old->stringize,
89				    $last->{d}->stringize);
90			}
91		} else {
92			delete $last->{d};
93		}
94	}
95	register_installation($n, $state);
96	return $borked;
97}
98
99sub perform_installation
100{
101	my ($handle, $state) = @_;
102
103	return if $state->defines('stub');
104
105	$state->{partial} = $handle->{partial};
106	$state->progress->visit_with_size($handle->{plist}, 'install');
107	if ($handle->{location}{early_close}) {
108		$handle->{location}->close_now;
109	} else {
110		$handle->{location}->finish_and_close;
111	}
112}
113
114sub perform_extraction
115{
116	my ($handle, $state) = @_;
117
118	return if $state->defines('stub');
119
120	$handle->{partial} = {};
121	$state->{partial} = $handle->{partial};
122	$state->{archive} = $handle->{location};
123	$state->{check_digest} = $handle->{plist}{check_digest};
124	my ($wanted, $tied) = ({}, {});
125	$handle->{plist}->find_extractible($state, $wanted, $tied);
126	my $p = $state->progress->new_sizer($handle->{plist}, $state);
127	while (my $file = $state->{archive}->next) {
128		if (keys %$wanted == 0) {
129			$state->tweak_header("skipping");
130			for my $e (values %$tied) {
131				$e->tie($state);
132				$p->advance($e);
133			}
134			if (keys %$tied > 0) {
135				# skipped entries should still be read in CACHE mode
136				if (defined $state->cache_directory) {
137					while (my $e = $state->{archive}->next) {
138					}
139				} else {
140					$handle->{location}{early_close} = 1;
141				}
142			}
143			last;
144		}
145		my $e = $tied->{$file->name};
146		if (defined $e) {
147			delete $tied->{$file->name};
148			$e->prepare_to_extract($state, $file);
149			$e->tie($state);
150			$state->{archive}->skip;
151			$p->advance($e);
152			# skip to next;
153			next;
154		}
155		$e = $wanted->{$file->name};
156		if (!defined $e) {
157			$state->fatal("archive member not found #1",
158			    $file->name);
159		}
160		delete $wanted->{$file->name};
161		my $fullname = $e->fullname;
162		if ($fullname =~ m,^$state->{localbase}/share/doc/pkg-readmes/,) {
163			push(@{$state->{readmes}}, $fullname);
164	}
165
166		$e->prepare_to_extract($state, $file);
167		$e->extract($state, $file);
168		$p->advance($e);
169	}
170	if (keys %$wanted > 0) {
171		$state->fatal("Truncated archive");
172	}
173	$p->saved;
174}
175
176my $user_tagged = {};
177
178sub extract_pkgname
179{
180	my $pkgname = shift;
181	$pkgname =~ s/^.*\///;
182	$pkgname =~ s/\.tgz$//;
183	return $pkgname;
184}
185
186sub tweak_package_status
187{
188	my ($pkgname, $state) = @_;
189
190	$pkgname = extract_pkgname($pkgname);
191	return 0 unless is_installed($pkgname);
192	return 0 unless $user_tagged->{$pkgname};
193	return 1 if $state->{not};
194	my $plist = OpenBSD::PackingList->from_installation($pkgname);
195	if ($plist->has('manual-installation') && $state->{automatic} > 1) {
196		delete $plist->{'manual-installation'};
197		$plist->to_installation;
198		return 1;
199	} elsif (!$plist->has('manual-installation') && !$state->{automatic}) {
200		OpenBSD::PackingElement::ManualInstallation->add($plist);
201		$plist->to_installation;
202		return 1;
203	}
204	return 0;
205}
206
207sub tweak_plist_status
208{
209	my ($plist, $state) = @_;
210
211	my $pkgname = $plist->pkgname;
212	if ($state->defines('FW_UPDATE')) {
213		$plist->has('firmware') or
214			OpenBSD::PackingElement::Firmware->add($plist);
215	}
216	return 0 unless $user_tagged->{$pkgname};
217	if (!$plist->has('manual-installation') && !$state->{automatic}) {
218		OpenBSD::PackingElement::ManualInstallation->add($plist);
219	}
220}
221
222sub tag_user_packages
223{
224	for my $set (@_) {
225		for my $n ($set->newer_names) {
226			$user_tagged->{OpenBSD::PackageName::url2pkgname($n)} = 1;
227		}
228	}
229}
230
231# used by newuser/newgroup to deal with options.
232package OpenBSD::PackingElement;
233use OpenBSD::Error;
234
235my ($uidcache, $gidcache);
236
237sub prepare_for_addition
238{
239}
240
241sub find_extractible
242{
243}
244
245sub extract
246{
247	my ($self, $state) = @_;
248	$state->{partial}{$self} = 1;
249	if ($state->{interrupted}) {
250		die "Interrupted";
251	}
252}
253
254sub install
255{
256	my ($self, $state) = @_;
257	# XXX "normal" items are already in partial, but NOT stuff
258	# that's install-only, like symlinks and dirs...
259	$state->{partial}{$self} = 1;
260	if ($state->{interrupted}) {
261		die "Interrupted";
262	}
263}
264
265sub copy_info
266{
267}
268
269sub set_modes
270{
271	my ($self, $state, $name) = @_;
272
273	if (defined $self->{owner} || defined $self->{group}) {
274		require OpenBSD::IdCache;
275
276		if (!defined $uidcache) {
277			$uidcache = OpenBSD::UidCache->new;
278			$gidcache = OpenBSD::GidCache->new;
279		}
280		my ($uid, $gid) = (-1, -1);
281		if (defined $self->{owner}) {
282			$uid = $uidcache->lookup($self->{owner}, $uid);
283		}
284		if (defined $self->{group}) {
285			$gid = $gidcache->lookup($self->{group}, $gid);
286		}
287		chown $uid, $gid, $name;
288	}
289	if (defined $self->{mode}) {
290		my $v = $self->{mode};
291		if ($v =~ m/^\d+$/o) {
292			chmod oct($v), $name;
293		} else {
294			$state->system(OpenBSD::Paths->chmod,
295			    $self->{mode}, $name);
296		}
297	}
298	if (defined $self->{ts}) {
299		utime $self->{ts}, $self->{ts}, $name;
300	}
301}
302
303package OpenBSD::PackingElement::Meta;
304
305# XXX stuff that's invisible to find_extractible should be considered extracted
306# for the most part, otherwise we create broken partial packages
307sub find_extractible
308{
309	my ($self, $state, $wanted, $tied) = @_;
310	$state->{partial}{$self} = 1;
311}
312
313package OpenBSD::PackingElement::ExtraInfo;
314use OpenBSD::Error;
315
316sub prepare_for_addition
317{
318	my ($self, $state, $pkgname) = @_;
319
320	if ($state->{ftp_only} && $self->{ftp} ne 'yes') {
321	    $state->errsay("Package #1 is not for ftp", $pkgname);
322	    $state->{problems}++;
323	}
324}
325
326package OpenBSD::PackingElement::NewAuth;
327use OpenBSD::Error;
328
329sub add_entry
330{
331	shift;	# get rid of self
332	my $l = shift;
333	while (@_ >= 2) {
334		my $f = shift;
335		my $v = shift;
336		next if !defined $v or $v eq '';
337		if ($v =~ m/^\!(.*)$/o) {
338			push(@$l, $f, $1);
339		} else {
340			push(@$l, $f, $v);
341		}
342	}
343}
344
345sub prepare_for_addition
346{
347	my ($self, $state, $pkgname) = @_;
348	my $ok = $self->check;
349	if (defined $ok) {
350		if ($ok == 0) {
351			$state->errsay("#1 #2 does not match",
352			    $self->type, $self->name);
353			$state->{problems}++;
354		}
355	}
356	$self->{okay} = $ok;
357}
358
359sub install
360{
361	my ($self, $state) = @_;
362	$self->SUPER::install($state);
363	my $auth = $self->name;
364	$state->say("adding #1 #2", $self->type, $auth) if $state->verbose >= 2;
365	return if $state->{not};
366	return if defined $self->{okay};
367	my $l=[];
368	push(@$l, "-v") if $state->verbose >= 2;
369	$self->build_args($l);
370	$state->vsystem($self->command,, @$l, '--', $auth);
371}
372
373package OpenBSD::PackingElement::NewUser;
374
375sub command 	{ OpenBSD::Paths->useradd }
376
377sub build_args
378{
379	my ($self, $l) = @_;
380
381	$self->add_entry($l,
382	    '-u', $self->{uid},
383	    '-g', $self->{group},
384	    '-L', $self->{class},
385	    '-c', $self->{comment},
386	    '-d', $self->{home},
387	    '-s', $self->{shell});
388}
389
390package OpenBSD::PackingElement::NewGroup;
391
392sub command { OpenBSD::Paths->groupadd }
393
394sub build_args
395{
396	my ($self, $l) = @_;
397
398	$self->add_entry($l, '-g', $self->{gid});
399}
400
401package OpenBSD::PackingElement::FileBase;
402use OpenBSD::Error;
403use File::Basename;
404use File::Path;
405use OpenBSD::Temp;
406
407sub find_extractible
408{
409	my ($self, $state, $wanted, $tied) = @_;
410	if ($self->{tieto} || $self->{link} || $self->{symlink}) {
411		$tied->{$self->name} = $self;
412	} else {
413		$wanted->{$self->name} = $self;
414	}
415}
416
417sub prepare_for_addition
418{
419	my ($self, $state, $pkgname) = @_;
420	my $fname = $self->retrieve_fullname($state, $pkgname);
421	# check for collisions with existing stuff
422	if ($state->vstat->exists($fname)) {
423		push(@{$state->{colliding}}, $self);
424		$self->{newly_found} = $pkgname;
425		$state->{problems}++;
426		return;
427	}
428	return if $state->defines('stub');
429	my $s = $state->vstat->add($fname,
430	    $self->{tieto} ? 0 : $self->retrieve_size, $pkgname);
431	return unless defined $s;
432	if ($s->ro) {
433		$s->report_ro($state, $fname);
434	}
435	if ($s->avail < 0) {
436		$s->report_overflow($state, $fname);
437	}
438}
439
440sub prepare_to_extract
441{
442	my ($self, $state, $file) = @_;
443	my $fullname = $self->fullname;
444	my $destdir = $state->{destdir};
445
446	$file->{cwd} = $self->cwd;
447	if (!$file->validate_meta($self)) {
448		$state->fatal("can't continue");
449	}
450
451	$file->set_name($fullname);
452	$file->{destdir} = $destdir;
453}
454
455sub find_safe_dir
456{
457	my ($self, $filename) = @_;
458	# figure out a safe directory where to put the temp file
459	my $d = dirname($filename);
460
461	# we go back up until we find an existing directory.
462	# hopefully this will be on the same file system.
463	my @candidates = ();
464	while (!-d $d) {
465		push(@candidates, $d);
466		$d = dirname($d);
467	}
468	# and now we try to go back down, creating the best path we can
469	while (@candidates > 0) {
470		my $c = pop @candidates;
471		last if -e $c; # okay this exists, but is not a directory
472		$d = $c;
473	}
474	return $d;
475}
476
477sub create_temp
478{
479	my ($self, $d, $state, $fullname) = @_;
480	# XXX this takes over right after find_safe_dir
481	if (!-e _) {
482		$state->make_path($d, $fullname);
483	}
484	my ($fh, $tempname) = OpenBSD::Temp::permanent_file($d, "pkg");
485	$self->{tempname} = $tempname;
486	if (!defined $tempname) {
487		if ($state->allow_nonroot($fullname)) {
488			$state->errsay("Can't create temp file outside localbase for #1", $fullname);
489			$state->errsay(OpenBSD::Temp->last_error);
490			return undef;
491		}
492		$state->fatal(OpenBSD::Temp->last_error);
493	}
494	return ($fh, $tempname);
495}
496
497sub tie
498{
499	my ($self, $state) = @_;
500	if (defined $self->{link} || defined $self->{symlink}) {
501		return;
502	}
503
504	$self->SUPER::extract($state);
505
506	my $d = $self->find_safe_dir($state->{destdir}.$self->fullname);
507	if ($state->{not}) {
508		$state->say("link #1 -> #2",
509		    $self->name, $d) if $state->verbose >= 3;
510	} else {
511		my ($fh, $tempname) = $self->create_temp($d, $state,
512		    $self->fullname);
513
514		return if !defined $tempname;
515		my $src = $self->{tieto}->realname($state);
516		unlink($tempname);
517		$state->say("link #1 -> #2", $src, $tempname)
518		    if $state->verbose >= 3;
519		link($src, $tempname) || $state->copy_file($src, $tempname);
520	}
521}
522
523sub extract
524{
525	my ($self, $state, $file) = @_;
526
527	$self->SUPER::extract($state);
528
529	my $d = $self->find_safe_dir($file->{destdir}.$file->name);
530	if ($state->{not}) {
531		$state->say("extract #1 -> #2",
532		    $self->name, $d) if $state->verbose >= 3;
533		$state->{archive}->skip;
534	} else {
535		my ($fh, $tempname) = $self->create_temp($d, $state,
536		    $file->name);
537		if (!defined $tempname) {
538			$state->{archive}->skip;
539			return;
540		}
541
542		# XXX don't apply destdir twice
543		$file->{destdir} = '';
544		$file->set_name($tempname);
545
546		$state->say("extract #1 -> #2", $self->name, $tempname)
547		    if $state->verbose >= 3;
548
549
550		if (!$file->isFile) {
551			$state->fatal("can't extract #1, it's not a file",
552			    $self->stringize);
553		}
554		$file->create;
555		$self->may_check_digest($file, $state);
556	}
557}
558
559sub install
560{
561	my ($self, $state) = @_;
562	$self->SUPER::install($state);
563	my $fullname = $self->fullname;
564	my $destdir = $state->{destdir};
565	if ($state->{not}) {
566		$state->say("moving tempfile -> #1",
567		    $destdir.$fullname) if $state->verbose >= 5;
568		return;
569	}
570	$state->make_path(dirname($destdir.$fullname), $fullname);
571	if (defined $self->{link}) {
572		link($destdir.$self->{link}, $destdir.$fullname);
573	} elsif (defined $self->{symlink}) {
574		symlink($self->{symlink}, $destdir.$fullname);
575	} else {
576		if (!defined $self->{tempname}) {
577			return if $state->allow_nonroot($fullname);
578			$state->fatal("No tempname for #1", $fullname);
579		}
580		rename($self->{tempname}, $destdir.$fullname) or
581		    $state->fatal("can't move #1 to #2: #3",
582			$self->{tempname}, $fullname, $!);
583		$state->say("moving #1 -> #2",
584		    $self->{tempname}, $destdir.$fullname)
585			if $state->verbose >= 5;
586		delete $self->{tempname};
587	}
588	$self->set_modes($state, $destdir.$fullname);
589}
590
591package OpenBSD::PackingElement::RcScript;
592sub install
593{
594	my ($self, $state) = @_;
595	$state->{add_rcscripts}{$self->fullname} = 1;
596	$self->SUPER::install($state);
597}
598
599package OpenBSD::PackingElement::Sample;
600use OpenBSD::Error;
601use File::Copy;
602
603sub prepare_for_addition
604{
605	my ($self, $state, $pkgname) = @_;
606	if (!defined $self->{copyfrom}) {
607		$state->errsay("\@sample element #1 does not reference a valid file",
608		    $self->fullname);
609		$state->{problems}++;
610	}
611	my $fname = $state->{destdir}.$self->fullname;
612	# If file already exists, we won't change it
613	if ($state->vstat->exists($fname)) {
614		return;
615	}
616	my $size = $self->{copyfrom}->{size};
617	my $s = $state->vstat->add($fname, $size, $pkgname);
618	return unless defined $s;
619	if ($s->ro) {
620		$s->report_ro($state, $fname);
621	}
622	if ($s->avail < 0) {
623		$s->report_overflow($state, $fname);
624	}
625}
626
627sub find_extractible
628{
629}
630
631sub extract
632{
633}
634
635sub install
636{
637	my ($self, $state) = @_;
638
639	$self->SUPER::install($state);
640	my $destdir = $state->{destdir};
641	my $filename = $destdir.$self->fullname;
642	my $orig = $self->{copyfrom};
643	my $origname = $destdir.$orig->fullname;
644	if (-e $filename) {
645		if ($state->verbose) {
646		    $state->say("The existing file #1 has NOT been changed",
647		    	$filename);
648		    if (defined $orig->{d}) {
649
650			# XXX assume this would be the same type of file
651			my $d = $self->compute_digest($filename, $orig->{d});
652			if ($d->equals($orig->{d})) {
653			    $state->say("(but it seems to match the sample file #1)", $origname);
654			} else {
655			    $state->say("It does NOT match the sample file #1",
656				$origname);
657			    $state->say("You may wish to update it manually");
658			}
659		    }
660		}
661	} else {
662		if ($state->{not}) {
663			$state->say("The file #1 would be installed from #2",
664			    $filename, $origname) if $state->verbose >= 2;
665		} else {
666			if (!copy($origname, $filename)) {
667				$state->errsay("File #1 could not be installed:\n\t#2", $filename, $!);
668			}
669			$self->set_modes($state, $filename);
670			if ($state->verbose >= 2) {
671			    $state->say("installed #1 from #2",
672				$filename, $origname);
673			}
674		}
675	}
676}
677
678package OpenBSD::PackingElement::Sampledir;
679sub extract
680{
681}
682
683sub install
684{
685	&OpenBSD::PackingElement::Dir::install;
686}
687
688package OpenBSD::PackingElement::Mandir;
689
690sub install
691{
692	my ($self, $state) = @_;
693	$self->SUPER::install($state);
694	if (!$state->{current_set}{known_mandirs}{$self->fullname}) {
695		$state->log("You may wish to add #1 to /etc/man.conf",
696		    $self->fullname);
697	}
698}
699
700package OpenBSD::PackingElement::Manpage;
701
702sub install
703{
704	my ($self, $state) = @_;
705	$self->SUPER::install($state);
706	$self->register_manpage($state, 'addman');
707}
708
709package OpenBSD::PackingElement::InfoFile;
710use File::Basename;
711use OpenBSD::Error;
712
713sub install
714{
715	my ($self, $state) = @_;
716	$self->SUPER::install($state);
717	return if $state->{not};
718	my $fullname = $state->{destdir}.$self->fullname;
719	$state->vsystem(OpenBSD::Paths->install_info,
720	    "--info-dir=".dirname($fullname), '--', $fullname);
721}
722
723package OpenBSD::PackingElement::Shell;
724sub install
725{
726	my ($self, $state) = @_;
727	$self->SUPER::install($state);
728	return if $state->{not};
729	my $fullname = $self->fullname;
730	my $destdir = $state->{destdir};
731	# go append to /etc/shells if needed
732	open(my $shells, '<', $destdir.OpenBSD::Paths->shells) or return;
733	while(<$shells>) {
734		s/^\#.*//o;
735		return if m/^\Q$fullname\E\s*$/;
736	}
737	close($shells);
738	open(my $shells2, '>>', $destdir.OpenBSD::Paths->shells) or return;
739	print $shells2 $fullname, "\n";
740	close $shells2;
741	$state->say("Shell #1 appended to #2", $fullname,
742	    $destdir.OpenBSD::Paths->shells) if $state->verbose;
743}
744
745package OpenBSD::PackingElement::Dir;
746sub extract
747{
748	my ($self, $state) = @_;
749	my $fullname = $self->fullname;
750	my $destdir = $state->{destdir};
751
752	return if -e $destdir.$fullname;
753	$self->SUPER::extract($state);
754	$state->say("new directory #1", $destdir.$fullname)
755	    if $state->verbose >= 3;
756	return if $state->{not};
757	$state->make_path($destdir.$fullname, $fullname);
758}
759
760sub install
761{
762	my ($self, $state) = @_;
763	$self->SUPER::install($state);
764	my $fullname = $self->fullname;
765	my $destdir = $state->{destdir};
766
767	$state->say("new directory #1", $destdir.$fullname)
768	    if $state->verbose >= 5;
769	return if $state->{not};
770	$state->make_path($destdir.$fullname, $fullname);
771	$self->set_modes($state, $destdir.$fullname);
772}
773
774package OpenBSD::PackingElement::Exec;
775use OpenBSD::Error;
776
777sub install
778{
779	my ($self, $state) = @_;
780
781	$self->SUPER::install($state);
782	if ($self->should_run($state)) {
783		$self->run($state);
784	}
785}
786
787sub should_run() { 1 }
788
789package OpenBSD::PackingElement::ExecAdd;
790sub should_run
791{
792	my ($self, $state) = @_;
793	return !$state->replacing;
794}
795
796package OpenBSD::PackingElement::ExecUpdate;
797sub should_run
798{
799	my ($self, $state) = @_;
800	return $state->replacing;
801}
802
803package OpenBSD::PackingElement::Tag;
804
805sub install
806{
807	my ($self, $state) = @_;
808
809	for my $d (@{$self->{definition_list}}) {
810		$d->add_tag($self, "install", $state);
811	}
812}
813
814package OpenBSD::PackingElement::Lib;
815
816sub install
817{
818	my ($self, $state) = @_;
819	$self->SUPER::install($state);
820	$self->mark_ldconfig_directory($state);
821}
822
823package OpenBSD::PackingElement::SpecialFile;
824use OpenBSD::PackageInfo;
825use OpenBSD::Error;
826
827sub copy_info
828{
829	my ($self, $dest, $state) = @_;
830	require File::Copy;
831
832	File::Copy::move($self->fullname, $dest) or
833	    $state->errsay("Problem while moving #1 into #2: #3",
834		$self->fullname, $dest, $!);
835}
836
837sub extract
838{
839	my ($self, $state) = @_;
840	$self->may_verify_digest($state);
841}
842
843sub find_extractible
844{
845	my ($self, $state) = @_;
846	$self->may_verify_digest($state);
847}
848
849package OpenBSD::PackingElement::FCONTENTS;
850sub copy_info
851{
852}
853
854package OpenBSD::PackingElement::AskUpdate;
855sub prepare_for_addition
856{
857	my ($self, $state, $pkgname, $set) = @_;
858	my @old = $set->older_names;
859	if ($self->spec->match_ref(\@old) > 0) {
860		my $key = "update_".OpenBSD::PackageName::splitstem($pkgname);
861		return if $state->defines($key);
862		if ($state->is_interactive) {
863			if ($state->confirm_defaults_to_no(
864			    "#1: #2.\nDo you want to update now",
865			    $pkgname, $self->{message})) {
866			    	return;
867			}
868		} else {
869			$state->errsay("Can't update #1 now: #2",
870			    $pkgname, $self->{message});
871		}
872		$state->{problems}++;
873	}
874}
875
876package OpenBSD::PackingElement::FDISPLAY;
877sub install
878{
879	my ($self, $state) = @_;
880	my $d = $self->{d};
881	if (!$state->{current_set}{known_displays}{$self->{d}->key}) {
882		$self->prepare($state);
883	}
884	$self->SUPER::install($state);
885}
886
887package OpenBSD::PackingElement::FUNDISPLAY;
888sub find_extractible
889{
890	my ($self, $state, $wanted, $tied) = @_;
891	$state->{current_set}{known_displays}{$self->{d}->key} = 1;
892	$self->SUPER::find_extractible($state, $wanted, $tied);
893}
894
8951;
896