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