1# ex:ts=8 sw=4:
2# $OpenBSD: PackingElement.pm,v 1.277 2020/06/09 20:16:12 sthen 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
21use OpenBSD::PackageInfo;
22use OpenBSD::Paths;
23
24# perl ipc
25require 5.008_000;
26
27# This is the basic class, which is mostly abstract, except for
28# create and register_with_factory.
29# It does provide base methods for stuff under it, though.
30package OpenBSD::PackingElement;
31our %keyword;
32
33sub create
34{
35	my ($class, $line, $plist) = @_;
36	if ($line =~ m/^\@(\S+)\s*(.*)$/o) {
37		if (defined $keyword{$1}) {
38			$keyword{$1}->add($plist, $2);
39		} else {
40			die "Unknown element: $line";
41		}
42	} else {
43		chomp $line;
44		OpenBSD::PackingElement::File->add($plist, $line);
45	}
46}
47
48sub register_with_factory
49{
50	my ($class, $k, $o) = @_;
51	if (!defined $k) {
52		$k = $class->keyword;
53	}
54	if (!defined $o) {
55		$o = $class;
56	}
57	$keyword{$k} = $o;
58}
59
60sub category() { 'items' }
61
62sub new
63{
64	my ($class, $args) = @_;
65	bless { name => $args }, $class;
66}
67
68sub remove
69{
70	my ($self, $plist) = @_;
71	$self->{deleted} = 1;
72}
73
74sub clone
75{
76	my $object = shift;
77	# shallow copy
78	my %h = %$object;
79	bless \%h, ref($object);
80}
81
82
83sub register_manpage
84{
85}
86
87sub destate
88{
89}
90
91sub add_object
92{
93	my ($self, $plist) = @_;
94	$self->destate($plist->{state});
95	$plist->add2list($self);
96	return $self;
97}
98
99sub add
100{
101	my ($class, $plist, @args) = @_;
102
103	my $self = $class->new(@args);
104	return $self->add_object($plist);
105}
106
107sub needs_keyword() { 1 }
108
109sub write
110{
111	my ($self, $fh) = @_;
112	my $s = $self->stringize;
113	if ($self->needs_keyword) {
114		$s = " $s" unless $s eq '';
115		print $fh "\@", $self->keyword, "$s\n";
116	} else {
117		print $fh "$s\n";
118	}
119}
120
121sub write_no_sig
122{
123	my ($self, $fh) = @_;
124	$self->write($fh);
125}
126
127sub write_without_variation
128{
129	my ($self, $fh) = @_;
130	$self->write_no_sig($fh);
131}
132
133# needed for comment checking
134sub fullstring
135{
136	my ($self, $fh) = @_;
137	my $s = $self->stringize;
138	if ($self->needs_keyword) {
139		$s = " $s" unless $s eq '';
140		return "\@".$self->keyword.$s;
141	} else {
142		return $s;
143	}
144}
145
146sub name
147{
148	my $self = shift;
149	return $self->{name};
150}
151
152sub set_name
153{
154	my ($self, $v) = @_;
155	$self->{name} = $v;
156}
157sub stringize
158{
159	my $self = shift;
160	return $self->name;
161}
162
163sub IsFile() { 0 }
164
165sub is_a_library() { 0 }
166sub NoDuplicateNames() { 0 }
167
168
169sub copy_shallow_if
170{
171	my ($self, $copy, $h) = @_;
172	$self->add_object($copy) if defined $h->{$self};
173}
174
175sub copy_deep_if
176{
177	my ($self, $copy, $h) = @_;
178	$self->clone->add_object($copy) if defined $h->{$self};
179}
180
181sub finish
182{
183	my ($class, $state) = @_;
184	OpenBSD::PackingElement::Fontdir->finish($state);
185	OpenBSD::PackingElement::RcScript->report($state);
186	if (defined $state->{readmes}) {
187		$state->say("New and changed readme(s):");
188		for my $file (sort @{$state->{readmes}}) {
189			$state->say("\t#1", $file);
190		}
191	}
192}
193
194# Basic class hierarchy
195
196# various stuff that's only linked to objects before/after them
197# this class doesn't have real objects: no valid new nor clone...
198package OpenBSD::PackingElement::Annotation;
199our @ISA=qw(OpenBSD::PackingElement);
200sub new { die "Can't create annotation objects" }
201
202# concrete objects
203package OpenBSD::PackingElement::Object;
204our @ISA=qw(OpenBSD::PackingElement);
205
206sub cwd
207{
208	return ${$_[0]->{cwd}};
209}
210
211sub absolute_okay() { 0 }
212sub compute_fullname
213{
214	my ($self, $state) = @_;
215
216	$self->{cwd} = $state->{cwd};
217	$self->set_name(File::Spec->canonpath($self->name));
218	if ($self->name =~ m|^/|) {
219		unless ($self->absolute_okay) {
220			die "Absolute name forbidden: ", $self->name;
221		}
222	}
223}
224
225sub make_full
226{
227	my ($self, $path) = @_;
228	if ($path !~ m|^/|o && $self->cwd ne '.') {
229		$path = $self->cwd."/".$path;
230		$path =~ s,^//,/,;
231	}
232	return $path;
233}
234
235sub fullname
236{
237	my $self = shift;
238	return $self->make_full($self->name);
239}
240
241sub compute_modes
242{
243	my ($self, $state) = @_;
244	if (defined $state->{mode}) {
245		$self->{mode} = $state->{mode};
246	}
247	if (defined $state->{owner}) {
248		$self->{owner} = $state->{owner};
249		if (defined $state->{uid}) {
250			$self->{uid} = $state->{uid};
251		}
252	}
253	if (defined $state->{group}) {
254		$self->{group} = $state->{group};
255		if (defined $state->{gid}) {
256			$self->{gid} = $state->{gid};
257		}
258	}
259}
260
261# concrete objects with file-like behavior
262package OpenBSD::PackingElement::FileObject;
263our @ISA=qw(OpenBSD::PackingElement::Object);
264
265sub NoDuplicateNames() { 1 }
266
267sub dirclass() { undef }
268
269sub new
270{
271	my ($class, $args) = @_;
272	if ($args =~ m/^(.*?)\/+$/o and defined $class->dirclass) {
273		bless { name => $1 }, $class->dirclass;
274	} else {
275		bless { name => $args }, $class;
276	}
277}
278
279sub destate
280{
281	my ($self, $state) = @_;
282	$state->{lastfileobject} = $self;
283	$self->compute_fullname($state);
284}
285
286sub set_tempname
287{
288	my ($self, $tempname) = @_;
289	$self->{tempname} = $tempname;
290}
291
292sub realname
293{
294	my ($self, $state) = @_;
295
296	my $name = $self->fullname;
297	if (defined $self->{tempname}) {
298		$name = $self->{tempname};
299	}
300	return $state->{destdir}.$name;
301}
302
303sub compute_digest
304{
305	my ($self, $filename, $class) = @_;
306	require OpenBSD::md5;
307	$class = 'OpenBSD::sha' if !defined $class;
308	return $class->new($filename);
309}
310
311# exec/unexec and friends
312package OpenBSD::PackingElement::Action;
313our @ISA=qw(OpenBSD::PackingElement::Object);
314
315# persistent state for following objects
316package OpenBSD::PackingElement::State;
317our @ISA=qw(OpenBSD::PackingElement::Object);
318
319# meta information, stored elsewhere
320package OpenBSD::PackingElement::Meta;
321our @ISA=qw(OpenBSD::PackingElement);
322
323package OpenBSD::PackingElement::Unique;
324our @ISA=qw(OpenBSD::PackingElement::Meta);
325
326sub add_object
327{
328	my ($self, $plist) = @_;
329
330	$self->destate($plist->{state});
331	$plist->addunique($self);
332	return $self;
333}
334
335sub remove
336{
337	my ($self, $plist) = @_;
338	delete $plist->{$self->category};
339}
340
341sub category
342{
343	return ref(shift);
344}
345
346# all the stuff that ends up in signatures
347package OpenBSD::PackingElement::VersionElement;
348our @ISA=qw(OpenBSD::PackingElement::Meta);
349
350# all dependency information
351package OpenBSD::PackingElement::Depend;
352our @ISA=qw(OpenBSD::PackingElement::VersionElement);
353
354# Abstract class for all file-like elements
355package OpenBSD::PackingElement::FileBase;
356our @ISA=qw(OpenBSD::PackingElement::FileObject);
357
358use File::Basename;
359
360sub write
361{
362	my ($self, $fh) = @_;
363	print $fh "\@comment no checksum\n" if defined $self->{nochecksum};
364	print $fh "\@comment no debug\n" if defined $self->{nodebug};
365	$self->SUPER::write($fh);
366	if (defined $self->{d}) {
367		$self->{d}->write($fh);
368	}
369	if (defined $self->{size}) {
370		print $fh "\@size ", $self->{size}, "\n";
371	}
372	if (defined $self->{ts}) {
373		print $fh "\@ts ", $self->{ts}, "\n";
374	}
375	if (defined $self->{symlink}) {
376		print $fh "\@symlink ", $self->{symlink}, "\n";
377	}
378	if (defined $self->{link}) {
379		print $fh "\@link ", $self->{link}, "\n";
380	}
381	if (defined $self->{tempname}) {
382		print $fh "\@temp ", $self->{tempname}, "\n";
383	}
384}
385
386sub destate
387{
388	my ($self, $state) = @_;
389	$self->SUPER::destate($state);
390	$state->{lastfile} = $self;
391	$state->{lastchecksummable} = $self;
392	$self->compute_modes($state);
393	if (defined $state->{nochecksum}) {
394		$self->{nochecksum} = 1;
395		undef $state->{nochecksum};
396	}
397	if (defined $state->{nodebug}) {
398		$self->{nodebug} = 1;
399		undef $state->{nodebug};
400	}
401}
402
403sub add_digest
404{
405	my ($self, $d) = @_;
406	$self->{d} = $d;
407}
408sub add_size
409{
410	my ($self, $sz) = @_;
411	$self->{size} = $sz;
412}
413
414sub add_timestamp
415{
416	my ($self, $ts) = @_;
417	$self->{ts} = $ts;
418}
419
420# XXX symlink/hardlinks are properties of File,
421# because we want to use inheritance for other stuff.
422
423sub make_symlink
424{
425	my ($self, $linkname) = @_;
426	$self->{symlink} = $linkname;
427}
428
429sub make_hardlink
430{
431	my ($self, $linkname) = @_;
432	$self->{link} = $linkname;
433}
434
435sub may_check_digest
436{
437	my ($self, $file, $state) = @_;
438	if ($state->{check_digest}) {
439		$self->check_digest($file, $state);
440	}
441}
442
443sub check_digest
444{
445	my ($self, $file, $state) = @_;
446	return if $self->{link} or $self->{symlink};
447	if (!defined $self->{d}) {
448		$state->log->fatal($state->f("#1 does not have a signature",
449		    $self->fullname));
450	}
451	my $d = $self->compute_digest($file->{destdir}.$file->name);
452	if (!$d->equals($self->{d})) {
453		$state->log->fatal($state->f("checksum for #1 does not match",
454		    $self->fullname));
455	}
456	if ($state->verbose >= 3) {
457		$state->say("Checksum match for #1", $self->fullname);
458	}
459}
460
461sub IsFile() { 1 }
462
463package OpenBSD::PackingElement::FileWithDebugInfo;
464our @ISA=qw(OpenBSD::PackingElement::FileBase);
465
466package OpenBSD::PackingElement::File;
467our @ISA=qw(OpenBSD::PackingElement::FileBase);
468
469use OpenBSD::PackageInfo qw(is_info_name);
470sub keyword() { "file" }
471__PACKAGE__->register_with_factory;
472
473sub dirclass() { "OpenBSD::PackingElement::Dir" }
474
475sub needs_keyword
476{
477	my $self = shift;
478	return $self->stringize =~ m/\^@/;
479}
480
481sub add_object
482{
483	my ($self, $plist) = @_;
484
485	$self->destate($plist->{state});
486	my $j = is_info_name($self->name);
487	if ($j && $self->cwd eq '.') {
488		bless $self, "OpenBSD::PackingElement::$j";
489		$self->add_object($plist);
490	} else {
491		$plist->add2list($self);
492	}
493	return $self;
494}
495
496package OpenBSD::PackingElement::Sample;
497our @ISA=qw(OpenBSD::PackingElement::FileObject);
498
499sub keyword() { "sample" }
500sub absolute_okay() { 1 }
501__PACKAGE__->register_with_factory;
502
503sub destate
504{
505	my ($self, $state) = @_;
506	if ($state->{lastfile}->isa("OpenBSD::PackingElement::SpecialFile")) {
507		die "Can't \@sample a specialfile: ",
508		    $state->{lastfile}->stringize;
509	}
510	$self->{copyfrom} = $state->{lastfile};
511	$self->compute_fullname($state);
512	$self->compute_modes($state);
513}
514
515sub dirclass() { "OpenBSD::PackingElement::Sampledir" }
516
517package OpenBSD::PackingElement::Ghost;
518our @ISA = qw(OpenBSD::PackingElement::FileObject);
519
520sub keyword() { "ghost" }
521sub absolute_okay() { 1 }
522__PACKAGE__->register_with_factory;
523
524sub destate
525{
526	my ($self, $state) = @_;
527	$self->compute_fullname($state);
528	$self->compute_modes($state);
529}
530
531package OpenBSD::PackingElement::Sampledir;
532our @ISA=qw(OpenBSD::PackingElement::DirBase OpenBSD::PackingElement::Sample);
533
534sub absolute_okay() { 1 }
535
536sub destate
537{
538	my ($self, $state) = @_;
539	$self->compute_fullname($state);
540	$self->compute_modes($state);
541}
542
543package OpenBSD::PackingElement::RcScript;
544use File::Basename;
545our @ISA = qw(OpenBSD::PackingElement::FileBase);
546
547sub keyword() { "rcscript" }
548sub absolute_okay() { 1 }
549__PACKAGE__->register_with_factory;
550
551sub destate
552{
553	my ($self, $state) = @_;
554	$self->compute_fullname($state);
555	$state->{lastfile} = $self;
556	$state->{lastchecksummable} = $self;
557	$self->compute_modes($state);
558}
559
560sub report
561{
562	my ($class, $state) = @_;
563
564	my @l;
565	for my $script (sort keys %{$state->{add_rcscripts}}) {
566		next if $state->{delete_rcscripts}{$script};
567		push(@l, $script);
568	}
569	if (@l > 0) {
570		$state->say("The following new rcscripts were installed: #1",
571		    join(' ', @l));
572		$state->say("See rcctl(8) for details.");
573	}
574}
575
576package OpenBSD::PackingElement::InfoFile;
577our @ISA=qw(OpenBSD::PackingElement::FileBase);
578
579sub keyword() { "info" }
580__PACKAGE__->register_with_factory;
581sub dirclass() { "OpenBSD::PackingElement::Infodir" }
582
583package OpenBSD::PackingElement::Shell;
584our @ISA=qw(OpenBSD::PackingElement::FileWithDebugInfo);
585
586sub keyword() { "shell" }
587__PACKAGE__->register_with_factory;
588
589package OpenBSD::PackingElement::Manpage;
590use File::Basename;
591our @ISA=qw(OpenBSD::PackingElement::FileBase);
592
593sub keyword() { "man" }
594__PACKAGE__->register_with_factory;
595
596sub register_manpage
597{
598	my ($self, $state, $key) = @_;
599	# XXX don't bother register stuff from partial packages
600	return if defined $self->{tempname};
601	my $fname = $self->fullname;
602	if ($fname =~ m,^(.*/man(?:/\w+)?)/((?:man|cat)[1-9n]\w*/.*),) {
603		push(@{$state->{$key}{$1}}, $2);
604    	}
605}
606
607sub is_source
608{
609	my $self = shift;
610	return $self->name =~ m/man\/man[^\/]+\/[^\/]+\.[\dln][^\/]?$/o;
611}
612
613sub source_to_dest
614{
615	my $self = shift;
616	my $v = $self->name;
617	$v =~ s/(man\/)man([^\/]+\/[^\/]+)\.[\dln][^\/]?$/$1cat$2.0/;
618	return $v;
619}
620
621# assumes the source is nroff, launches nroff
622sub format
623{
624	my ($self, $state, $dest, $destfh) = @_;
625
626	my $base = $state->{base};
627	my $fname = $base.$self->fullname;
628	if (-z $fname) {
629		$state->error("empty source manpage: #1", $fname);
630		return;
631	}
632	open(my $fh, '<', $fname) or die "Can't read $fname: $!";
633	my $line = <$fh>;
634	close $fh;
635	my @extra = ();
636	# extra preprocessors as described in man.
637	if ($line =~ m/^\'\\\"\s+(.*)$/o) {
638		for my $letter (split '', $1) {
639			if ($letter =~ m/[ept]/o) {
640				push(@extra, "-$letter");
641			} elsif ($letter eq 'r') {
642				push(@extra, "-R");
643			}
644		}
645	}
646	my $d = dirname($dest);
647	unless (-d $d) {
648		mkdir($d);
649	}
650	if (my ($dir, $file) = $fname =~ m/^(.*)\/([^\/]+\/[^\/]+)$/) {
651		my $r = $state->system(sub {
652		    open STDOUT, '>&', $destfh or
653			die "Can't write to $dest: $!";
654		    close $destfh;
655		    chdir($dir) or die "Can't chdir to $dir: $!";
656		    },
657		    $state->{groff} // OpenBSD::Paths->groff,
658		    qw(-mandoc -mtty-char -E -Ww -Tascii -P -c),
659		    @extra, '--', $file);
660		if ($r != 0) {
661			# system already displays an error message
662			return;
663		}
664	} else {
665		$state->error("Can't parse source name #1", $fname);
666		return;
667	}
668	return 1;
669}
670
671package OpenBSD::PackingElement::Lib;
672our @ISA=qw(OpenBSD::PackingElement::FileWithDebugInfo);
673
674our $todo = 0;
675
676sub keyword() { "lib" }
677__PACKAGE__->register_with_factory;
678
679sub mark_ldconfig_directory
680{
681	my ($self, $state) = @_;
682	$state->ldconfig->mark_directory($self->fullname);
683}
684
685sub parse
686{
687	my ($self, $filename) = @_;
688	if ($filename =~ m/^(.*?)\/?lib([^\/]+)\.so\.(\d+)\.(\d+)$/o) {
689		return ($2, $3, $4, $1);
690	} else {
691		return undef;
692	}
693}
694
695sub is_a_library() { 1 }
696
697package OpenBSD::PackingElement::Binary;
698our @ISA=qw(OpenBSD::PackingElement::FileWithDebugInfo);
699
700sub keyword() { "bin" }
701__PACKAGE__->register_with_factory;
702
703package OpenBSD::PackingElement::StaticLib;
704our @ISA=qw(OpenBSD::PackingElement::FileWithDebugInfo);
705
706sub keyword() { "static-lib" }
707__PACKAGE__->register_with_factory;
708
709package OpenBSD::PackingElement::SharedObject;
710our @ISA=qw(OpenBSD::PackingElement::FileWithDebugInfo);
711
712sub keyword() { "so" }
713__PACKAGE__->register_with_factory;
714
715package OpenBSD::PackingElement::PkgConfig;
716our @ISA=qw(OpenBSD::PackingElement::FileBase);
717
718sub keyword() { "pkgconfig" }
719__PACKAGE__->register_with_factory;
720
721package OpenBSD::PackingElement::LibtoolLib;
722our @ISA=qw(OpenBSD::PackingElement::FileBase);
723
724sub keyword() { "ltlib" }
725__PACKAGE__->register_with_factory;
726
727# Comment is very special
728package OpenBSD::PackingElement::Comment;
729our @ISA=qw(OpenBSD::PackingElement::Meta);
730
731sub keyword() { "comment" }
732__PACKAGE__->register_with_factory;
733
734sub destate
735{
736	my ($self, $state) = @_;
737	$self->{cwd} = $state->{cwd};
738}
739
740sub add
741{
742	my ($class, $plist, $args) = @_;
743
744	if ($args =~ m/^\$OpenBSD.*\$\s*$/o) {
745		return OpenBSD::PackingElement::CVSTag->add($plist, $args);
746	} elsif ($args =~ m/^(?:subdir|pkgpath)\=(.*?)\s+cdrom\=(.*?)\s+ftp\=(.*?)\s*$/o) {
747		return OpenBSD::PackingElement::ExtraInfo->add($plist, $1, $2, $3);
748	} elsif ($args =~ m/^(?:subdir|pkgpath)\=(.*?)\s+ftp\=(.*?)\s*$/o) {
749		return OpenBSD::PackingElement::ExtraInfo->add($plist, $1, undef, $2);
750	} elsif ($args eq 'no checksum') {
751		$plist->{state}{nochecksum} = 1;
752		return;
753	} elsif ($args eq 'no debug') {
754		$plist->{state}{nodebug} = 1;
755		return;
756	} else {
757		return $class->SUPER::add($plist, $args);
758	}
759}
760
761package OpenBSD::PackingElement::CVSTag;
762our @ISA=qw(OpenBSD::PackingElement::Meta);
763
764sub keyword() { 'comment' }
765
766sub category() { 'cvstags'}
767
768# don't incorporate this into compared signatures
769sub write_without_variation
770{
771}
772
773package OpenBSD::PackingElement::sha;
774our @ISA=qw(OpenBSD::PackingElement::Annotation);
775
776__PACKAGE__->register_with_factory('sha');
777
778sub add
779{
780	my ($class, $plist, $args) = @_;
781
782	require OpenBSD::md5;
783
784	$plist->{state}->{lastchecksummable}->add_digest(OpenBSD::sha->fromstring($args));
785	return;
786}
787
788package OpenBSD::PackingElement::symlink;
789our @ISA=qw(OpenBSD::PackingElement::Annotation);
790
791__PACKAGE__->register_with_factory('symlink');
792
793sub add
794{
795	my ($class, $plist, $args) = @_;
796
797	$plist->{state}->{lastfile}->make_symlink($args);
798	return;
799}
800
801package OpenBSD::PackingElement::hardlink;
802our @ISA=qw(OpenBSD::PackingElement::Annotation);
803
804__PACKAGE__->register_with_factory('link');
805
806sub add
807{
808	my ($class, $plist, $args) = @_;
809
810	$plist->{state}->{lastfile}->make_hardlink($args);
811	return;
812}
813
814package OpenBSD::PackingElement::temp;
815our @ISA=qw(OpenBSD::PackingElement::Annotation);
816
817__PACKAGE__->register_with_factory('temp');
818
819sub add
820{
821	my ($class, $plist, $args) = @_;
822	$plist->{state}->{lastfile}->set_tempname($args);
823	return;
824}
825
826package OpenBSD::PackingElement::size;
827our @ISA=qw(OpenBSD::PackingElement::Annotation);
828
829__PACKAGE__->register_with_factory('size');
830
831sub add
832{
833	my ($class, $plist, $args) = @_;
834
835	$plist->{state}->{lastfile}->add_size($args);
836	return;
837}
838
839package OpenBSD::PackingElement::ts;
840our @ISA=qw(OpenBSD::PackingElement::Annotation);
841
842__PACKAGE__->register_with_factory('ts');
843
844sub add
845{
846	my ($class, $plist, $args) = @_;
847
848	$plist->{state}->{lastfile}->add_timestamp($args);
849	return;
850}
851
852package OpenBSD::PackingElement::Option;
853our @ISA=qw(OpenBSD::PackingElement::Meta);
854
855sub keyword() { 'option' }
856__PACKAGE__->register_with_factory;
857
858sub new
859{
860	my ($class, $args) = @_;
861	if ($args eq 'no-default-conflict') {
862		return OpenBSD::PackingElement::NoDefaultConflict->new;
863	} elsif ($args eq 'manual-installation') {
864		return OpenBSD::PackingElement::ManualInstallation->new;
865	} elsif ($args eq 'firmware') {
866		return OpenBSD::PackingElement::Firmware->new;
867	} elsif ($args eq 'always-update') {
868		return OpenBSD::PackingElement::AlwaysUpdate->new;
869	} elsif ($args eq 'is-branch') {
870		return OpenBSD::PackingElement::IsBranch->new;
871	} else {
872		die "Unknown option: $args";
873	}
874}
875
876package OpenBSD::PackingElement::UniqueOption;
877our @ISA=qw(OpenBSD::PackingElement::Unique OpenBSD::PackingElement::Option);
878
879sub stringize
880{
881	my $self = shift;
882	return $self->category;
883}
884
885sub new
886{
887	my ($class, @args) = @_;
888	bless {}, $class;
889}
890
891package OpenBSD::PackingElement::NoDefaultConflict;
892our @ISA=qw(OpenBSD::PackingElement::UniqueOption);
893
894sub category() { 'no-default-conflict' }
895
896package OpenBSD::PackingElement::ManualInstallation;
897our @ISA=qw(OpenBSD::PackingElement::UniqueOption);
898
899sub category() { 'manual-installation' }
900
901# XXX don't incorporate this in signatures.
902sub write_no_sig()
903{
904}
905
906package OpenBSD::PackingElement::Firmware;
907our @ISA=qw(OpenBSD::PackingElement::ManualInstallation);
908sub category() { 'firmware' }
909
910package OpenBSD::PackingElement::AlwaysUpdate;
911our @ISA=qw(OpenBSD::PackingElement::UniqueOption);
912
913sub category()
914{
915	'always-update';
916}
917
918package OpenBSD::PackingElement::IsBranch;
919our @ISA=qw(OpenBSD::PackingElement::UniqueOption);
920
921sub category()
922{
923	'is-branch';
924}
925# The special elements that don't end in the right place
926package OpenBSD::PackingElement::ExtraInfo;
927our @ISA=qw(OpenBSD::PackingElement::Unique OpenBSD::PackingElement::Comment);
928
929sub category() { 'extrainfo' }
930
931sub new
932{
933	my ($class, $subdir, $cdrom, $ftp) = @_;
934
935	$ftp =~ s/^\"(.*)\"$/$1/;
936	$ftp =~ s/^\'(.*)\'$/$1/;
937	my $o = bless { subdir => $subdir,
938	    path => OpenBSD::PkgPath->new($subdir),
939	    ftp => $ftp}, $class;
940	if (defined $cdrom) {
941		$cdrom =~ s/^\"(.*)\"$/$1/;
942		$cdrom =~ s/^\'(.*)\'$/$1/;
943		$o->{cdrom} = $cdrom;
944	}
945	return $o;
946}
947
948sub subdir
949{
950	return shift->{subdir};
951}
952
953sub may_quote
954{
955	my $s = shift;
956	if ($s =~ m/\s/) {
957		return '"'.$s.'"';
958	} else {
959		return $s;
960	}
961}
962
963sub stringize
964{
965	my $self = shift;
966	my @l = (
967	    "pkgpath=".$self->{subdir});
968	if (defined $self->{cdrom}) {
969		push @l, "cdrom=".may_quote($self->{cdrom});
970	}
971	push(@l, "ftp=".may_quote($self->{ftp}));
972	return join(' ', @l);
973}
974
975package OpenBSD::PackingElement::Name;
976use File::Spec;
977our @ISA=qw(OpenBSD::PackingElement::Unique);
978
979sub keyword() { "name" }
980__PACKAGE__->register_with_factory;
981sub category() { "name" }
982
983package OpenBSD::PackingElement::LocalBase;
984our @ISA=qw(OpenBSD::PackingElement::Unique);
985
986sub keyword() { "localbase" }
987__PACKAGE__->register_with_factory;
988sub category() { "localbase" }
989
990package OpenBSD::PackingElement::Url;
991our @ISA=qw(OpenBSD::PackingElement::Unique);
992
993sub keyword() { "url" }
994__PACKAGE__->register_with_factory;
995sub category() { "url" }
996
997# XXX don't incorporate this in signatures.
998sub write_no_sig()
999{
1000}
1001
1002package OpenBSD::PackingElement::Version;
1003our @ISA=qw(OpenBSD::PackingElement::Unique OpenBSD::PackingElement::VersionElement);
1004
1005sub keyword() { "version" }
1006__PACKAGE__->register_with_factory;
1007sub category() { "version" }
1008
1009package OpenBSD::PackingElement::Conflict;
1010our @ISA=qw(OpenBSD::PackingElement::Meta);
1011
1012sub keyword() { "conflict" }
1013__PACKAGE__->register_with_factory;
1014sub category() { "conflict" }
1015
1016sub spec
1017{
1018	my $self =shift;
1019
1020	require OpenBSD::Search;
1021	return OpenBSD::Search::PkgSpec->new($self->name);
1022}
1023
1024package OpenBSD::PackingElement::Dependency;
1025our @ISA=qw(OpenBSD::PackingElement::Depend);
1026use OpenBSD::Error;
1027
1028sub keyword() { "depend" }
1029__PACKAGE__->register_with_factory;
1030sub category() { "depend" }
1031
1032sub new
1033{
1034	my ($class, $args) = @_;
1035	my ($pkgpath, $pattern, $def) = split /\:/o, $args;
1036	bless { name => $def, pkgpath => $pkgpath, pattern => $pattern,
1037	    def => $def }, $class;
1038}
1039
1040sub stringize
1041{
1042	my $self = shift;
1043	return join(':', map { $self->{$_}}
1044	    (qw(pkgpath pattern def)));
1045}
1046
1047OpenBSD::Auto::cache(spec,
1048    sub {
1049	require OpenBSD::Search;
1050
1051	my $self = shift;
1052	return OpenBSD::Search::PkgSpec->new($self->{pattern})
1053	    ->add_pkgpath_hint($self->{pkgpath});
1054    });
1055
1056package OpenBSD::PackingElement::Wantlib;
1057our @ISA=qw(OpenBSD::PackingElement::Depend);
1058
1059sub category() { "wantlib" }
1060sub keyword() { "wantlib" }
1061__PACKAGE__->register_with_factory;
1062
1063OpenBSD::Auto::cache(spec,
1064    sub {
1065    	my $self = shift;
1066
1067    	require OpenBSD::LibSpec;
1068	return OpenBSD::LibSpec->from_string($self->name);
1069    });
1070
1071package OpenBSD::PackingElement::Libset;
1072our @ISA=qw(OpenBSD::PackingElement::Meta);
1073
1074sub category() { "libset" }
1075sub keyword() { "libset" }
1076__PACKAGE__->register_with_factory;
1077
1078sub new
1079{
1080	my ($class, $args) = @_;
1081	if ($args =~ m/(.*)\:(.*)/) {
1082		return bless {name => $1, libs => [split(/\,/, $2)]}, $class;
1083	} else {
1084		die "Bad args for libset: $args";
1085	}
1086}
1087
1088sub stringize
1089{
1090	my $self = shift;
1091	return $self->{name}.':'.join(',', @{$self->{libs}});
1092}
1093
1094package OpenBSD::PackingElement::PkgPath;
1095our @ISA=qw(OpenBSD::PackingElement::Meta);
1096
1097sub keyword() { "pkgpath" }
1098__PACKAGE__->register_with_factory;
1099sub category() { "pkgpath" }
1100
1101sub new
1102{
1103	my ($class, $fullpkgpath) = @_;
1104	bless {name => $fullpkgpath,
1105	    path => OpenBSD::PkgPath::WithOpts->new($fullpkgpath)}, $class;
1106}
1107
1108sub subdir
1109{
1110	return shift->{name};
1111}
1112
1113package OpenBSD::PackingElement::AskUpdate;
1114our @ISA=qw(OpenBSD::PackingElement::Meta);
1115
1116sub new
1117{
1118	my ($class, $args) = @_;
1119	my ($pattern, $message) = split /\s+/o, $args, 2;
1120	bless { pattern => $pattern, message => $message}, $class;
1121}
1122
1123sub stringize
1124{
1125	my $self = shift;
1126	return join(' ', map { $self->{$_}}
1127	    (qw(pattern message)));
1128}
1129
1130sub keyword() { "ask-update" }
1131__PACKAGE__->register_with_factory;
1132sub category() { "ask-update" }
1133
1134OpenBSD::Auto::cache(spec,
1135    sub {
1136	require OpenBSD::PkgSpec;
1137
1138	my $self = shift;
1139	return OpenBSD::PkgSpec->new($self->{pattern})
1140    });
1141
1142package OpenBSD::PackingElement::NewAuth;
1143our @ISA=qw(OpenBSD::PackingElement::Action);
1144
1145package OpenBSD::PackingElement::NewUser;
1146our @ISA=qw(OpenBSD::PackingElement::NewAuth);
1147
1148sub type() { "user" }
1149sub category() { "users" }
1150sub keyword() { "newuser" }
1151__PACKAGE__->register_with_factory;
1152
1153sub new
1154{
1155	my ($class, $args) = @_;
1156	my ($name, $uid, $group, $loginclass, $comment, $home, $shell) =
1157	    split /\:/o, $args;
1158	bless { name => $name, uid => $uid, group => $group,
1159	    class => $loginclass,
1160	    comment => $comment, home => $home, shell => $shell }, $class;
1161}
1162
1163sub destate
1164{
1165	my ($self, $state) = @_;
1166	my $uid = $self->{uid};
1167	$uid =~ s/^\!//;
1168	$state->{owners}{$self->{name}} = $uid;
1169}
1170
1171sub check
1172{
1173	my $self = shift;
1174	my ($name, $passwd, $uid, $gid, $quota, $class, $gcos, $dir, $shell,
1175	    $expire) = getpwnam($self->name);
1176	return unless defined $name;
1177	if ($self->{uid} =~ m/^\!(.*)$/o) {
1178		return 0 unless $uid == $1;
1179	}
1180	if ($self->{group} =~ m/^\!(.*)$/o) {
1181		my $g = $1;
1182		unless ($g =~ m/^\d+$/o) {
1183			$g = getgrnam($g);
1184			return 0 unless defined $g;
1185		}
1186		return 0 unless $gid eq $g;
1187	}
1188	if ($self->{class} =~ m/^\!(.*)$/o) {
1189		return 0 unless $class eq $1;
1190	}
1191	if ($self->{comment} =~ m/^\!(.*)$/o) {
1192		return 0 unless $gcos eq $1;
1193	}
1194	if ($self->{home} =~ m/^\!(.*)$/o) {
1195		return 0 unless $dir eq $1;
1196	}
1197	if ($self->{shell} =~ m/^\!(.*)$/o) {
1198		return 0 unless $shell eq $1;
1199	}
1200	return 1;
1201}
1202
1203sub stringize
1204{
1205	my $self = shift;
1206	return join(':', map { $self->{$_}}
1207	    (qw(name uid group class comment home shell)));
1208}
1209
1210package OpenBSD::PackingElement::NewGroup;
1211our @ISA=qw(OpenBSD::PackingElement::NewAuth);
1212
1213
1214sub type() { "group" }
1215sub category() { "groups" }
1216sub keyword() { "newgroup" }
1217__PACKAGE__->register_with_factory;
1218
1219sub new
1220{
1221	my ($class, $args) = @_;
1222	my ($name, $gid) = split /\:/o, $args;
1223	bless { name => $name, gid => $gid }, $class;
1224}
1225
1226sub destate
1227{
1228	my ($self, $state) = @_;
1229	my $gid = $self->{gid};
1230	$gid =~ s/^\!//;
1231	$state->{groups}{$self->{name}} = $gid;
1232}
1233
1234sub check
1235{
1236	my $self = shift;
1237	my ($name, $passwd, $gid, $members) = getgrnam($self->name);
1238	return unless defined $name;
1239	if ($self->{gid} =~ m/^\!(.*)$/o) {
1240		return 0 unless $gid == $1;
1241	}
1242	return 1;
1243}
1244
1245sub stringize($)
1246{
1247	my $self = $_[0];
1248	return join(':', map { $self->{$_}}
1249	    (qw(name gid)));
1250}
1251
1252package OpenBSD::PackingElement::Cwd;
1253use File::Spec;
1254our @ISA=qw(OpenBSD::PackingElement::State);
1255
1256
1257sub keyword() { 'cwd' }
1258__PACKAGE__->register_with_factory;
1259
1260sub destate
1261{
1262	my ($self, $state) = @_;
1263	$state->set_cwd($self->name);
1264}
1265
1266package OpenBSD::PackingElement::Owner;
1267our @ISA=qw(OpenBSD::PackingElement::State);
1268
1269sub keyword() { 'owner' }
1270__PACKAGE__->register_with_factory;
1271
1272sub destate
1273{
1274	my ($self, $state) = @_;
1275
1276	delete $state->{uid};
1277	if ($self->name eq '') {
1278		undef $state->{owner};
1279	} else {
1280		$state->{owner} = $self->name;
1281		if (defined $state->{owners}{$self->name}) {
1282			$state->{uid} = $state->{owners}{$self->name};
1283		}
1284	}
1285}
1286
1287package OpenBSD::PackingElement::Group;
1288our @ISA=qw(OpenBSD::PackingElement::State);
1289
1290sub keyword() { 'group' }
1291__PACKAGE__->register_with_factory;
1292
1293sub destate
1294{
1295	my ($self, $state) = @_;
1296
1297	delete $state->{gid};
1298	if ($self->name eq '') {
1299		undef $state->{group};
1300	} else {
1301		$state->{group} = $self->name;
1302		if (defined $state->{groups}{$self->name}) {
1303			$state->{gid} = $state->{groups}{$self->name};
1304		}
1305	}
1306}
1307
1308package OpenBSD::PackingElement::Mode;
1309our @ISA=qw(OpenBSD::PackingElement::State);
1310
1311sub keyword() { 'mode' }
1312__PACKAGE__->register_with_factory;
1313
1314sub destate
1315{
1316	my ($self, $state) = @_;
1317
1318	if ($self->name eq '') {
1319		undef $state->{mode};
1320	} else {
1321		$state->{mode} = $self->name;
1322	}
1323}
1324
1325package OpenBSD::PackingElement::ExeclikeAction;
1326use File::Basename;
1327use OpenBSD::Error;
1328our @ISA=qw(OpenBSD::PackingElement::Action);
1329
1330sub command
1331{
1332	my $self = shift;
1333	return $self->name;
1334}
1335
1336sub expand
1337{
1338	my ($self, $state) = @_;
1339	my $e = $self->command;
1340	if ($e =~ m/\%F/o) {
1341		die "Bad expand" unless defined $state->{lastfile};
1342		$e =~ s/\%F/$state->{lastfile}->{name}/g;
1343	}
1344	if ($e =~ m/\%D/o) {
1345		die "Bad expand" unless defined $state->{cwd};
1346		$e =~ s/\%D/$state->cwd/ge;
1347	}
1348	if ($e =~ m/\%B/o) {
1349		die "Bad expand" unless defined $state->{lastfile};
1350		$e =~ s/\%B/dirname($state->{lastfile}->fullname)/ge;
1351	}
1352	if ($e =~ m/\%f/o) {
1353		die "Bad expand" unless defined $state->{lastfile};
1354		$e =~ s/\%f/basename($state->{lastfile}->fullname)/ge;
1355	}
1356	return $e;
1357}
1358
1359sub destate
1360{
1361	my ($self, $state) = @_;
1362	$self->{expanded} = $self->expand($state);
1363}
1364
1365sub run
1366{
1367	my ($self, $state, $v) = @_;
1368
1369	$v //= $self->{expanded};
1370	$state->ldconfig->ensure;
1371	$state->say("#1 #2", $self->keyword, $v) if $state->verbose >= 2;
1372	$state->log->system(OpenBSD::Paths->sh, '-c', $v) unless $state->{not};
1373}
1374
1375# so tags are going to get triggered by packages we depend on.
1376# turns out it's simpler to have them as "actions" because that's basically
1377# what's going to happen, so destate is good for them, gives us access
1378# to things like %D
1379package OpenBSD::PackingElement::TagBase;
1380our @ISA=qw(OpenBSD::PackingElement::ExeclikeAction);
1381
1382sub command
1383{
1384	my $self = shift;
1385	return $self->{params};
1386}
1387
1388package OpenBSD::PackingElement::Tag;
1389our @ISA=qw(OpenBSD::PackingElement::TagBase);
1390sub keyword() { 'tag' }
1391
1392__PACKAGE__->register_with_factory;
1393
1394sub new
1395{
1396	my ($class, $args) = @_;
1397	my ($tag, $params) = split(/\s+/, $args, 2);
1398	bless {
1399		name => $tag,
1400		params => $params // '',
1401	    }, $class;
1402}
1403
1404sub stringize
1405{
1406	my $self = shift;
1407	if ($self->{params} ne '') {
1408		return join(' ', $self->name, $self->{params});
1409	} else {
1410		return $self->name;
1411	}
1412}
1413
1414# tags are a kind of dependency, we have a special list for them, BUT
1415# they're still part of the normal packing-list
1416sub add_object
1417{
1418	my ($self, $plist) = @_;
1419	push(@{$plist->{tags}}, $self);
1420	$self->SUPER::add_object($plist);
1421}
1422
1423# and the define tag thingy is very similar... the main difference being
1424# how it's actually registered
1425package OpenBSD::PackingElement::DefineTag;
1426our @ISA=qw(OpenBSD::PackingElement::TagBase);
1427
1428sub category() {'define-tag'}
1429sub keyword() { 'define-tag' }
1430__PACKAGE__->register_with_factory;
1431
1432# define-tag may be parsed several times, but these objects must be
1433# unique for tag accumulation to work correctly
1434my $cache = {};
1435
1436my $subclass = {
1437	'at-end' => 'Atend',
1438	'supersedes' => 'Supersedes',
1439	'cleanup' => 'Cleanup' };
1440
1441sub new
1442{
1443	my ($class, $args) = @_;
1444	my ($tag, $mode, $params) = split(/\s+/, $args, 3);
1445	$cache->{$args} //= bless {
1446	    name => $tag,
1447	    mode => $mode,
1448	    params => $params,
1449	    }, $class;
1450}
1451
1452sub stringize
1453{
1454	my $self = shift;
1455	return join(' ', $self->name, $self->{mode}, $self->{params});
1456}
1457
1458sub add_object
1459{
1460	my ($self, $plist) = @_;
1461	my $sub = $subclass->{$self->{mode}};
1462	if (!defined $sub) {
1463		die "unknown mode for \@define-tag";
1464	}
1465	bless $self, "OpenBSD::PackingElement::DefineTag::$sub";
1466	push(@{$plist->{tags_definitions}{$self->name}}, $self);
1467	$self->SUPER::add_object($plist);
1468}
1469
1470sub destate
1471{
1472}
1473
1474package OpenBSD::PackingElement::DefineTag::Atend;
1475our @ISA = qw(OpenBSD::PackingElement::DefineTag);
1476
1477sub add_tag
1478{
1479	my ($self, $tag, $mode, $state) = @_;
1480	# add the tag contents if they exist
1481	# they're stored in a hash because the order doesn't matter
1482	if ($tag->{params} ne '') {
1483		$self->{list}{$tag->{expanded}} = 1;
1484	}
1485	# special case: we have to run things *now* if deleting
1486	if ($mode eq 'delete' && $tag->{found_in_self} && !$state->replacing) {
1487
1488		$self->run_tag($state)
1489		    unless $state->{tags}{superseded}{$self->name};
1490		delete $state->{tags}{atend}{$self->name};
1491	} else {
1492		$state->{tags}{atend}{$self->name} = $self;
1493	}
1494}
1495
1496sub run_tag
1497{
1498	my ($self, $state) = @_;
1499	my $command = $self->command;
1500	if ($command =~ m/\%D/) {
1501		$command =~ s/\%D/$state->{localbase}/g;
1502	}
1503
1504	if ($command =~ m/\%l/) {
1505		my $l = join(' ', keys %{$self->{list}});
1506		$command =~ s/\%l/$l/g;
1507	}
1508	if ($command =~ m/\%u/) {
1509		for my $p (keys %{$self->{list}}) {
1510			my $v = $command;
1511			$v =~ s/\%u/$p/g;
1512			$self->run($state, $v);
1513			$state->say("Running #1", $v)
1514			    if $state->defines("TRACE_TAGS");
1515		}
1516	} else {
1517		$self->run($state, $command);
1518		$state->say("Running #1", $command)
1519		    if $state->defines("TRACE_TAGS");
1520	}
1521}
1522
1523sub need_params
1524{
1525	my $self = shift;
1526	return $self->{params} =~ m/\%[lu]/;
1527}
1528
1529package OpenBSD::PackingElement::DefineTag::Cleanup;
1530our @ISA = qw(OpenBSD::PackingElement::DefineTag);
1531
1532sub add_tag
1533{
1534	my ($self, $tag, $mode, $state) = @_;
1535	# okay, we don't need to look at directories if we're not deleting
1536	return unless $mode eq 'delete';
1537	# this does not work at all like 'at-end'
1538	# instead we record a hash of directories we may want to cleanup
1539	push(@{$state->{tag_cleanup}{$tag->{expanded}}}, $self);
1540}
1541
1542sub need_params
1543{
1544	1
1545}
1546
1547package OpenBSD::PackingElement::DefineTag::Supersedes;
1548our @ISA = qw(OpenBSD::PackingElement::DefineTag);
1549
1550sub add_tag
1551{
1552	my ($self, $tag, $mode, $state) = @_;
1553	$state->{tags}{superseded}{$self->{params}} = 1;
1554}
1555
1556sub need_params
1557{
1558	0
1559}
1560
1561package OpenBSD::PackingElement::Exec;
1562our @ISA=qw(OpenBSD::PackingElement::ExeclikeAction);
1563
1564sub keyword() { "exec" }
1565__PACKAGE__->register_with_factory;
1566
1567package OpenBSD::PackingElement::ExecAlways;
1568our @ISA=qw(OpenBSD::PackingElement::Exec);
1569
1570sub keyword() { "exec-always" }
1571__PACKAGE__->register_with_factory;
1572
1573package OpenBSD::PackingElement::ExecAdd;
1574our @ISA=qw(OpenBSD::PackingElement::Exec);
1575
1576sub keyword() { "exec-add" }
1577__PACKAGE__->register_with_factory;
1578
1579package OpenBSD::PackingElement::ExecUpdate;
1580our @ISA=qw(OpenBSD::PackingElement::Exec);
1581
1582sub keyword() { "exec-update" }
1583__PACKAGE__->register_with_factory;
1584
1585package OpenBSD::PackingElement::Unexec;
1586our @ISA=qw(OpenBSD::PackingElement::ExeclikeAction);
1587
1588sub keyword() { "unexec" }
1589__PACKAGE__->register_with_factory;
1590
1591package OpenBSD::PackingElement::UnexecAlways;
1592our @ISA=qw(OpenBSD::PackingElement::Unexec);
1593
1594sub keyword() { "unexec-always" }
1595__PACKAGE__->register_with_factory;
1596
1597package OpenBSD::PackingElement::UnexecUpdate;
1598our @ISA=qw(OpenBSD::PackingElement::Unexec);
1599
1600sub keyword() { "unexec-update" }
1601__PACKAGE__->register_with_factory;
1602
1603package OpenBSD::PackingElement::UnexecDelete;
1604our @ISA=qw(OpenBSD::PackingElement::Unexec);
1605
1606sub keyword() { "unexec-delete" }
1607__PACKAGE__->register_with_factory;
1608
1609package OpenBSD::PackingElement::ExtraUnexec;
1610our @ISA=qw(OpenBSD::PackingElement::ExeclikeAction);
1611
1612sub keyword() { "extraunexec" }
1613__PACKAGE__->register_with_factory;
1614
1615package OpenBSD::PackingElement::DirlikeObject;
1616our @ISA=qw(OpenBSD::PackingElement::FileObject);
1617
1618package OpenBSD::PackingElement::DirBase;
1619our @ISA=qw(OpenBSD::PackingElement::DirlikeObject);
1620
1621sub destate
1622{
1623	my ($self, $state) = @_;
1624	$state->{lastdir} = $self;
1625	$self->SUPER::destate($state);
1626}
1627
1628
1629sub stringize
1630{
1631	my $self = shift;
1632	return $self->name."/";
1633}
1634
1635sub write
1636{
1637	my ($self, $fh) = @_;
1638	$self->SUPER::write($fh);
1639}
1640
1641package OpenBSD::PackingElement::Dir;
1642our @ISA=qw(OpenBSD::PackingElement::DirBase);
1643
1644sub keyword() { "dir" }
1645__PACKAGE__->register_with_factory;
1646
1647sub destate
1648{
1649	my ($self, $state) = @_;
1650	$self->SUPER::destate($state);
1651	$self->compute_modes($state);
1652}
1653
1654sub needs_keyword
1655{
1656	my $self = shift;
1657	return $self->stringize =~ m/\^@/o;
1658}
1659
1660package OpenBSD::PackingElement::Infodir;
1661our @ISA=qw(OpenBSD::PackingElement::Dir);
1662sub keyword() { "info" }
1663sub needs_keyword() { 1 }
1664
1665package OpenBSD::PackingElement::Fontdir;
1666our @ISA=qw(OpenBSD::PackingElement::Dir);
1667sub keyword() { "fontdir" }
1668__PACKAGE__->register_with_factory;
1669sub needs_keyword() { 1 }
1670sub dirclass() { "OpenBSD::PackingElement::Fontdir" }
1671
1672sub install
1673{
1674	my ($self, $state) = @_;
1675	$self->SUPER::install($state);
1676	$state->log("You may wish to update your font path for #1", $self->fullname);
1677	$state->{recorder}{fonts_todo}{$state->{destdir}.$self->fullname} = 1;
1678}
1679
1680sub reload
1681{
1682	my ($self, $state) = @_;
1683	$state->{recorder}{fonts_todo}{$state->{destdir}.$self->fullname} = 1;
1684}
1685
1686sub update_fontalias
1687{
1688	my ($state, $dirname) = @_;
1689
1690	my $alias_name = "$dirname/fonts.alias";
1691	if ($state->verbose > 1) {
1692		$state->say("Assembling #1 from #2",
1693		    $alias_name, "$alias_name-*");
1694	}
1695
1696	if (open my $out, '>', $alias_name) {
1697		for my $alias (glob "$alias_name-*") {
1698			if (open my $f ,'<', $alias) {
1699				print {$out} <$f>;
1700				close $f;
1701			} else {
1702				$state->errsay("Couldn't read #1: #2",
1703				    $alias, $!);
1704			}
1705		}
1706		close $out;
1707	} else {
1708		$state->errsay("Couldn't write #1: #2", $alias_name, $!);
1709	}
1710}
1711
1712sub restore_fontdir
1713{
1714	my ($state, $dirname) = @_;
1715	if (-f "$dirname/fonts.dir.dist") {
1716
1717		unlink("$dirname/fonts.dir");
1718		$state->copy_file("$dirname/fonts.dir.dist",
1719		    "$dirname/fonts.dir");
1720	}
1721}
1722
1723sub run_if_exists
1724{
1725	my ($state, $cmd, @l) = @_;
1726
1727	if (-x $cmd) {
1728		$state->vsystem($cmd, @l);
1729	} else {
1730		$state->errsay("#1 not found", $cmd);
1731	}
1732}
1733
1734sub finish
1735{
1736	my ($class, $state) = @_;
1737	return if $state->{not};
1738
1739	my @l = keys %{$state->{recorder}->{fonts_todo}};
1740	@l = grep {-d $_} @l;
1741
1742	if (@l != 0) {
1743		$state->print("Updating font cache: ") if $state->verbose < 2;
1744		require OpenBSD::Error;
1745
1746		map { update_fontalias($state, $_) } @l;
1747		run_if_exists($state, OpenBSD::Paths->mkfontscale, '--', @l);
1748		run_if_exists($state, OpenBSD::Paths->mkfontdir, '--', @l);
1749		map { restore_fontdir($state, $_) } @l;
1750
1751		run_if_exists($state, OpenBSD::Paths->fc_cache, '--', @l);
1752		$state->say("ok") if $state->verbose < 2;
1753	}
1754}
1755
1756
1757package OpenBSD::PackingElement::Mandir;
1758our @ISA=qw(OpenBSD::PackingElement::Dir);
1759
1760sub keyword() { "mandir" }
1761__PACKAGE__->register_with_factory;
1762sub needs_keyword() { 1 }
1763sub dirclass() { "OpenBSD::PackingElement::Mandir" }
1764
1765package OpenBSD::PackingElement::Extra;
1766our @ISA=qw(OpenBSD::PackingElement::FileObject);
1767
1768sub keyword() { 'extra' }
1769sub absolute_okay() { 1 }
1770__PACKAGE__->register_with_factory;
1771
1772sub destate
1773{
1774	my ($self, $state) = @_;
1775	$self->compute_fullname($state);
1776}
1777
1778sub dirclass() { "OpenBSD::PackingElement::Extradir" }
1779
1780package OpenBSD::PackingElement::Extradir;
1781our @ISA=qw(OpenBSD::PackingElement::DirBase OpenBSD::PackingElement::Extra);
1782sub absolute_okay() { 1 }
1783
1784sub destate
1785{
1786	&OpenBSD::PackingElement::Extra::destate;
1787}
1788
1789package OpenBSD::PackingElement::SpecialFile;
1790our @ISA=qw(OpenBSD::PackingElement::Unique);
1791
1792sub add_digest
1793{
1794	&OpenBSD::PackingElement::FileBase::add_digest;
1795}
1796
1797sub add_size
1798{
1799	&OpenBSD::PackingElement::FileBase::add_size;
1800}
1801
1802sub add_timestamp
1803{
1804	# just don't
1805}
1806
1807sub compute_digest
1808{
1809	&OpenBSD::PackingElement::FileObject::compute_digest;
1810}
1811
1812sub write
1813{
1814	&OpenBSD::PackingElement::FileBase::write;
1815}
1816
1817sub needs_keyword { 0 }
1818
1819sub add_object
1820{
1821	my ($self, $plist) = @_;
1822	$self->{infodir} = $plist->{infodir};
1823	$self->SUPER::add_object($plist);
1824}
1825
1826sub infodir
1827{
1828	my $self = shift;
1829	return ${$self->{infodir}};
1830}
1831
1832sub stringize
1833{
1834	my $self = shift;
1835	return $self->category;
1836}
1837
1838sub fullname
1839{
1840	my $self = shift;
1841	my $d = $self->infodir;
1842	if (defined $d) {
1843		return $d.$self->name;
1844	} else {
1845		return undef;
1846	}
1847}
1848
1849sub category
1850{
1851	my $self = shift;
1852
1853	return $self->name;
1854}
1855
1856sub new
1857{
1858	&OpenBSD::PackingElement::UniqueOption::new;
1859}
1860
1861sub may_verify_digest
1862{
1863	my ($self, $state) = @_;
1864	if (!$state->{check_digest}) {
1865		return;
1866	}
1867	if (!defined $self->{d}) {
1868		$state->log->fatal($state->f("#1 does not have a signature",
1869		    $self->fullname));
1870	}
1871	my $d = $self->compute_digest($self->fullname);
1872	if (!$d->equals($self->{d})) {
1873		$state->log->fatal($state->f("checksum for #1 does not match",
1874		    $self->fullname));
1875	}
1876	if ($state->verbose >= 3) {
1877		$state->say("Checksum match for #1", $self->fullname);
1878	}
1879}
1880
1881package OpenBSD::PackingElement::FCONTENTS;
1882our @ISA=qw(OpenBSD::PackingElement::SpecialFile);
1883sub name() { OpenBSD::PackageInfo::CONTENTS }
1884# XXX we don't write `self'
1885sub write
1886{}
1887
1888sub copy_shallow_if
1889{
1890}
1891
1892sub copy_deep_if
1893{
1894}
1895
1896# CONTENTS doesn't have a checksum
1897sub may_verify_digest
1898{
1899}
1900
1901package OpenBSD::PackingElement::FDESC;
1902our @ISA=qw(OpenBSD::PackingElement::SpecialFile);
1903sub name() { OpenBSD::PackageInfo::DESC }
1904
1905package OpenBSD::PackingElement::DisplayFile;
1906our @ISA=qw(OpenBSD::PackingElement::SpecialFile);
1907use OpenBSD::Error;
1908
1909sub prepare
1910{
1911	my ($self, $state) = @_;
1912	my $fname = $self->fullname;
1913	if (open(my $src, '<', $fname)) {
1914		while (<$src>) {
1915			chomp;
1916			next if m/^\+\-+\s*$/o;
1917			s/^[+-] //o;
1918			$state->log("#1", $_);
1919		}
1920	} else {
1921		$state->errsay("Can't open #1: #2", $fname, $!);
1922    	}
1923}
1924
1925package OpenBSD::PackingElement::FDISPLAY;
1926our @ISA=qw(OpenBSD::PackingElement::DisplayFile);
1927sub name() { OpenBSD::PackageInfo::DISPLAY }
1928
1929package OpenBSD::PackingElement::FUNDISPLAY;
1930our @ISA=qw(OpenBSD::PackingElement::DisplayFile);
1931sub name() { OpenBSD::PackageInfo::UNDISPLAY }
1932
1933package OpenBSD::PackingElement::Arch;
1934our @ISA=qw(OpenBSD::PackingElement::Unique);
1935
1936sub category() { 'arch' }
1937sub keyword() { 'arch' }
1938__PACKAGE__->register_with_factory;
1939
1940sub new
1941{
1942	my ($class, $args) = @_;
1943	my @arches= split(/\,/o, $args);
1944	bless { arches => \@arches }, $class;
1945}
1946
1947sub stringize($)
1948{
1949	my $self = $_[0];
1950	return join(',', @{$self->{arches}});
1951}
1952
1953sub check
1954{
1955	my ($self, $forced_arch) = @_;
1956
1957	for my $ok (@{$self->{arches}}) {
1958		return 1 if $ok eq '*';
1959		if (defined $forced_arch) {
1960			if ($ok eq $forced_arch) {
1961				return 1;
1962			} else {
1963				next;
1964			}
1965		}
1966		return 1 if $ok eq OpenBSD::Paths->machine_architecture;
1967		return 1 if $ok eq OpenBSD::Paths->architecture;
1968	}
1969	return;
1970}
1971
1972package OpenBSD::PackingElement::Signer;
1973our @ISA=qw(OpenBSD::PackingElement::Unique);
1974sub keyword() { 'signer' }
1975__PACKAGE__->register_with_factory;
1976sub category() { "signer" }
1977sub new
1978{
1979	my ($class, $args) = @_;
1980	unless ($args =~ m/^[\w\d\.\-\+\@]+$/) {
1981		die "Invalid characters in signer $args";
1982	}
1983	$class->SUPER::new($args);
1984}
1985
1986# don't incorporate this into compared signatures
1987sub write_without_variation
1988{
1989}
1990
1991# XXX digital-signatures have to be unique, since they are a part
1992# of the unsigned packing-list, with only the b64sig part removed
1993# (likewise for signer)
1994package OpenBSD::PackingElement::DigitalSignature;
1995our @ISA=qw(OpenBSD::PackingElement::Unique);
1996
1997sub keyword() { 'digital-signature' }
1998__PACKAGE__->register_with_factory;
1999sub category() { "digital-signature" }
2000
2001# parse to and from a subset of iso8601
2002#
2003# allows us to represent timestamps in a human readable format without
2004# any ambiguity
2005sub time_to_iso8601
2006{
2007	my $time = shift;
2008	my ($sec, $min, $hour, $day, $month, $year, @rest) = gmtime($time);
2009	return sprintf("%04d-%02d-%02dT%02d:%02d:%02dZ",
2010	    $year+1900, $month+1, $day, $hour, $min, $sec);
2011}
2012
2013sub iso8601
2014{
2015	my $self = shift;
2016	return time_to_iso8601($self->{timestamp});
2017}
2018
2019sub iso8601_to_time
2020{
2021	if ($_[0] =~ m/^(\d{4})\-(\d{2})\-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})Z$/) {
2022		my ($year, $month, $day, $hour, $min, $sec) =
2023			($1 - 1900, $2-1, $3, $4, $5, $6);
2024		require POSIX;
2025		my $oldtz = $ENV{TZ};
2026		$ENV{TZ} = 'UTC';
2027		my $t = POSIX::mktime($sec, $min, $hour, $day, $month, $year);
2028		if (defined $oldtz) {
2029			$ENV{TZ} = $oldtz;
2030		} else {
2031			delete $ENV{TZ};
2032		}
2033		return $t;
2034	} else {
2035		die "Incorrect ISO8601 timestamp: $_[0]";
2036	}
2037}
2038
2039sub new
2040{
2041	my ($class, $args) = @_;
2042	my ($key, $tsbase, $tsmin, $tssec, $signature) = split(/\:/, $args);
2043	my $timestamp = iso8601_to_time("$tsbase:$tsmin:$tssec");
2044	bless { key => $key, timestamp => $timestamp, b64sig => $signature },
2045		$class;
2046}
2047
2048sub blank
2049{
2050	my ($class, $type) = @_;
2051	bless { key => $type, timestamp => time, b64sig => '' }, $class;
2052}
2053
2054sub stringize
2055{
2056	my $self = shift;
2057	return join(':', $self->{key}, time_to_iso8601($self->{timestamp}),
2058	    $self->{b64sig});
2059}
2060
2061sub write_no_sig
2062{
2063	my ($self, $fh) = @_;
2064	print $fh "\@", $self->keyword, " ", $self->{key}, ":",
2065	    time_to_iso8601($self->{timestamp}), "\n";
2066}
2067
2068# don't incorporate this into compared signatures
2069sub write_without_variation
2070{
2071}
2072
2073package OpenBSD::PackingElement::Old;
2074our @ISA=qw(OpenBSD::PackingElement);
2075
2076my $warned;
2077
2078sub new
2079{
2080	my ($class, $k, $args) = @_;
2081	bless { keyword => $k, name => $args }, $class;
2082}
2083
2084sub add
2085{
2086	my ($o, $plist, $args) = @_;
2087	my $keyword = $$o;
2088	if (!$warned->{$keyword}) {
2089		print STDERR "Warning: obsolete construct: \@$keyword $args\n";
2090		$warned->{$keyword} = 1;
2091	}
2092	my $o2 = OpenBSD::PackingElement::Old->new($keyword, $args);
2093	$o2->add_object($plist);
2094	$plist->{deprecated} = 1;
2095	return undef;
2096}
2097
2098sub keyword
2099{
2100	my $self = shift;
2101	return $self->{keyword};
2102}
2103
2104sub register_old_keyword
2105{
2106	my ($class, $k) = @_;
2107	$class->register_with_factory($k, bless \$k, $class);
2108}
2109
2110for my $k (qw(src display mtree ignore_inst dirrm pkgcfl pkgdep newdepend
2111    libdepend endfake ignore vendor incompatibility md5 sysctl)) {
2112	__PACKAGE__->register_old_keyword($k);
2113}
2114
2115# Real pkgpath objects, with matching properties
2116package OpenBSD::PkgPath;
2117sub new
2118{
2119	my ($class, $fullpkgpath) = @_;
2120	my ($dir, @mandatory) = split(/\,/, $fullpkgpath);
2121	return bless {dir => $dir,
2122		mandatory => {map {($_, 1)} @mandatory},
2123	}, $class;
2124}
2125
2126sub fullpkgpath
2127{
2128	my ($self) = @_;
2129	if(%{$self->{mandatory}}) {
2130		my $m = join(",", keys %{$self->{mandatory}});
2131		return "$self->{dir},$m";
2132	} else {
2133		return $self->{dir};
2134	}
2135}
2136
2137# a pkgpath has a dir, and some flavors/multi parts. To match, we must
2138# remove them all. So, keep a full hash of everything we have (has), and
2139# when stuff $to_rm matches, remove them from $from.
2140# We match when we're left with nothing.
2141sub trim
2142{
2143	my ($self, $has, $from, $to_rm) = @_;
2144	for my $f (keys %$to_rm) {
2145		if ($has->{$f}) {
2146			delete $from->{$f};
2147		} else {
2148			return 0;
2149		}
2150	}
2151	return 1;
2152}
2153
2154# basic match: after mandatory, nothing left
2155sub match2
2156{
2157	my ($self, $has, $h) = @_;
2158	if (keys %$h) {
2159		return 0;
2160	} else {
2161		return 1;
2162	}
2163}
2164
2165# zap mandatory, check that what's left is okay.
2166sub match
2167{
2168	my ($self, $other) = @_;
2169	# make a copy of options
2170	my %h = %{$other->{mandatory}};
2171	if (!$self->trim($other->{mandatory}, \%h, $self->{mandatory})) {
2172		return 0;
2173	}
2174	if ($self->match2($other->{mandatory}, \%h)) {
2175		return 1;
2176	} else {
2177		return 0;
2178	}
2179}
2180
2181package OpenBSD::PkgPath::WithOpts;
2182our @ISA = qw(OpenBSD::PkgPath);
2183
2184sub new
2185{
2186	my ($class, $fullpkgpath) = @_;
2187	my @opts = ();
2188	while ($fullpkgpath =~ s/\[\,(.*?)\]//) {
2189		push(@opts, {map {($_, 1)} split(/\,/, $1) });
2190	};
2191	my $o = $class->SUPER::new($fullpkgpath);
2192	if (@opts == 0) {
2193		bless $o, "OpenBSD::PkgPath";
2194	} else {
2195		$o->{opts} = \@opts;
2196	}
2197	return $o;
2198}
2199
2200# match with options: systematically trim any optional part that  fully
2201# matches, until we're left with nothing, or some options keep happening.
2202sub match2
2203{
2204	my ($self, $has, $h) = @_;
2205	if (!keys %$h) {
2206		return 1;
2207	}
2208	for my $opts (@{$self->{opts}}) {
2209		my %h2 = %$h;
2210		if ($self->trim($has, \%h2, $opts)) {
2211			$h = \%h2;
2212			if (!keys %$h) {
2213				return 1;
2214			}
2215		}
2216	}
2217	return 0;
2218}
2219
22201;
2221