xref: /openbsd-src/usr.sbin/pkg_add/OpenBSD/PackingElement.pm (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1# ex:ts=8 sw=4:
2# $OpenBSD: PackingElement.pm,v 1.244 2016/06/25 18:02:59 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
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 ($state->{readmes}) {
187		$state->say("Look in #1/share/doc/pkg-readmes for extra documentation.", $state->{localbase});
188	}
189}
190
191# Basic class hierarchy
192
193# various stuff that's only linked to objects before/after them
194# this class doesn't have real objects: no valid new nor clone...
195package OpenBSD::PackingElement::Annotation;
196our @ISA=qw(OpenBSD::PackingElement);
197sub new { die "Can't create annotation objects" }
198
199# concrete objects
200package OpenBSD::PackingElement::Object;
201our @ISA=qw(OpenBSD::PackingElement);
202
203sub cwd
204{
205	return ${$_[0]->{cwd}};
206}
207
208sub absolute_okay() { 0 }
209sub compute_fullname
210{
211	my ($self, $state) = @_;
212
213	$self->{cwd} = $state->{cwd};
214	$self->set_name(File::Spec->canonpath($self->name));
215	if ($self->name =~ m|^/|) {
216		unless ($self->absolute_okay) {
217			die "Absolute name forbidden: ", $self->name;
218		}
219	}
220}
221
222sub make_full
223{
224	my ($self, $path) = @_;
225	if ($path !~ m|^/|o && $self->cwd ne '.') {
226		$path = $self->cwd."/".$path;
227		$path =~ s,^//,/,;
228	}
229	return $path;
230}
231
232sub fullname
233{
234	my $self = shift;
235	return $self->make_full($self->name);
236}
237
238sub compute_modes
239{
240	my ($self, $state) = @_;
241	if (defined $state->{mode}) {
242		$self->{mode} = $state->{mode};
243	}
244	if (defined $state->{owner}) {
245		$self->{owner} = $state->{owner};
246		if (defined $state->{uid}) {
247			$self->{uid} = $state->{uid};
248		}
249	}
250	if (defined $state->{group}) {
251		$self->{group} = $state->{group};
252		if (defined $state->{gid}) {
253			$self->{gid} = $state->{gid};
254		}
255	}
256}
257
258# concrete objects with file-like behavior
259package OpenBSD::PackingElement::FileObject;
260our @ISA=qw(OpenBSD::PackingElement::Object);
261
262sub NoDuplicateNames() { 1 }
263
264sub dirclass() { undef }
265
266sub new
267{
268	my ($class, $args) = @_;
269	if ($args =~ m/^(.*?)\/+$/o and defined $class->dirclass) {
270		bless { name => $1 }, $class->dirclass;
271	} else {
272		bless { name => $args }, $class;
273	}
274}
275
276sub destate
277{
278	my ($self, $state) = @_;
279	$state->{lastfileobject} = $self;
280	$self->compute_fullname($state);
281}
282
283sub set_tempname
284{
285	my ($self, $tempname) = @_;
286	$self->{tempname} = $tempname;
287}
288
289sub realname
290{
291	my ($self, $state) = @_;
292
293	my $name = $self->fullname;
294	if (defined $self->{tempname}) {
295		$name = $self->{tempname};
296	}
297	return $state->{destdir}.$name;
298}
299
300sub compute_digest
301{
302	my ($self, $filename, $class) = @_;
303	require OpenBSD::md5;
304	$class = 'OpenBSD::sha' if !defined $class;
305	return $class->new($filename);
306}
307
308sub write
309{
310	my ($self, $fh) = @_;
311
312	$self->SUPER::write($fh);
313	if (defined $self->{tags}) {
314		for my $tag (sort keys %{$self->{tags}}) {
315			print $fh "\@tag ", $tag, "\n";
316		}
317	}
318}
319
320# exec/unexec and friends
321package OpenBSD::PackingElement::Action;
322our @ISA=qw(OpenBSD::PackingElement::Object);
323
324# persistent state for following objects
325package OpenBSD::PackingElement::State;
326our @ISA=qw(OpenBSD::PackingElement::Object);
327
328# meta information, stored elsewhere
329package OpenBSD::PackingElement::Meta;
330our @ISA=qw(OpenBSD::PackingElement);
331
332package OpenBSD::PackingElement::Unique;
333our @ISA=qw(OpenBSD::PackingElement::Meta);
334
335sub add_object
336{
337	my ($self, $plist) = @_;
338
339	$self->destate($plist->{state});
340	$plist->addunique($self);
341	return $self;
342}
343
344sub remove
345{
346	my ($self, $plist) = @_;
347	delete $plist->{$self->category};
348}
349
350sub category
351{
352	return ref(shift);
353}
354
355# all dependency information
356package OpenBSD::PackingElement::Depend;
357our @ISA=qw(OpenBSD::PackingElement::Meta);
358
359# Abstract class for all file-like elements
360package OpenBSD::PackingElement::FileBase;
361our @ISA=qw(OpenBSD::PackingElement::FileObject);
362
363use File::Basename;
364
365sub write
366{
367	my ($self, $fh) = @_;
368	print $fh "\@comment no checksum\n" if defined $self->{nochecksum};
369	$self->SUPER::write($fh);
370	if (defined $self->{d}) {
371		$self->{d}->write($fh);
372	}
373	if (defined $self->{size}) {
374		print $fh "\@size ", $self->{size}, "\n";
375	}
376	if (defined $self->{ts}) {
377		print $fh "\@ts ", $self->{ts}, "\n";
378	}
379	if (defined $self->{symlink}) {
380		print $fh "\@symlink ", $self->{symlink}, "\n";
381	}
382	if (defined $self->{link}) {
383		print $fh "\@link ", $self->{link}, "\n";
384	}
385	if (defined $self->{tempname}) {
386		print $fh "\@temp ", $self->{tempname}, "\n";
387	}
388}
389
390sub destate
391{
392	my ($self, $state) = @_;
393	$self->SUPER::destate($state);
394	$state->{lastfile} = $self;
395	$state->{lastchecksummable} = $self;
396	$self->compute_modes($state);
397	if (defined $state->{nochecksum}) {
398		$self->{nochecksum} = 1;
399		undef $state->{nochecksum};
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::File;
464our @ISA=qw(OpenBSD::PackingElement::FileBase);
465
466use OpenBSD::PackageInfo qw(is_info_name);
467sub keyword() { "file" }
468__PACKAGE__->register_with_factory;
469
470sub dirclass() { "OpenBSD::PackingElement::Dir" }
471
472sub needs_keyword
473{
474	my $self = shift;
475	return $self->stringize =~ m/\^@/;
476}
477
478sub add_object
479{
480	my ($self, $plist) = @_;
481
482	$self->destate($plist->{state});
483	my $j = is_info_name($self->fullname);
484	if ($j) {
485		bless $self, "OpenBSD::PackingElement::$j";
486		$self->add_object($plist);
487	} else {
488		$plist->add2list($self);
489	}
490	return $self;
491}
492
493package OpenBSD::PackingElement::Sample;
494our @ISA=qw(OpenBSD::PackingElement::FileObject);
495
496sub keyword() { "sample" }
497sub absolute_okay() { 1 }
498__PACKAGE__->register_with_factory;
499
500sub destate
501{
502	my ($self, $state) = @_;
503	if ($state->{lastfile}->isa("OpenBSD::PackingElement::SpecialFile")) {
504		die "Can't \@sample a specialfile: ".
505		    $state->{lastfile}->stringize. "\n";
506	}
507	$self->{copyfrom} = $state->{lastfile};
508	$self->compute_fullname($state);
509	$self->compute_modes($state);
510}
511
512sub dirclass() { "OpenBSD::PackingElement::Sampledir" }
513
514package OpenBSD::PackingElement::Ghost;
515our @ISA = qw(OpenBSD::PackingElement::FileObject);
516
517sub keyword() { "ghost" }
518sub absolute_okay() { 1 }
519__PACKAGE__->register_with_factory;
520
521sub destate
522{
523	my ($self, $state) = @_;
524	$self->compute_fullname($state);
525	$self->compute_modes($state);
526}
527
528package OpenBSD::PackingElement::Sampledir;
529our @ISA=qw(OpenBSD::PackingElement::DirBase OpenBSD::PackingElement::Sample);
530
531sub absolute_okay() { 1 }
532
533sub destate
534{
535	my ($self, $state) = @_;
536	$self->compute_fullname($state);
537	$self->compute_modes($state);
538}
539
540package OpenBSD::PackingElement::RcScript;
541use File::Basename;
542our @ISA = qw(OpenBSD::PackingElement::FileBase);
543
544sub keyword() { "rcscript" }
545sub absolute_okay() { 1 }
546__PACKAGE__->register_with_factory;
547
548sub destate
549{
550	my ($self, $state) = @_;
551	$self->compute_fullname($state);
552	if ($self->name =~ m/^\//) {
553		$state->set_cwd(dirname($self->name));
554	}
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::FileBase);
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)/((?:man|cat).*),) {
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		$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		    OpenBSD::Paths->groff,
658		    qw(-mandoc -mtty-char -E -Ww -Tascii -P -c),
659		    @extra, '--', $file);
660	} else {
661		die "Can't parse source name $fname";
662	}
663	return 1;
664}
665
666package OpenBSD::PackingElement::Mandoc;
667our @ISA=qw(OpenBSD::PackingElement::Manpage);
668
669sub keyword() { "mandoc" }
670__PACKAGE__->register_with_factory;
671
672package OpenBSD::PackingElement::Lib;
673our @ISA=qw(OpenBSD::PackingElement::FileBase);
674
675our $todo = 0;
676
677sub keyword() { "lib" }
678__PACKAGE__->register_with_factory;
679
680sub mark_ldconfig_directory
681{
682	my ($self, $state) = @_;
683	$state->ldconfig->mark_directory($self->fullname);
684}
685
686sub parse
687{
688	my ($self, $filename) = @_;
689	if ($filename =~ m/^(.*?)\/?lib([^\/]+)\.so\.(\d+)\.(\d+)$/o) {
690		return ($2, $3, $4, $1);
691	} else {
692		return undef;
693	}
694}
695
696sub is_a_library() { 1 }
697
698package OpenBSD::PackingElement::PkgConfig;
699our @ISA=qw(OpenBSD::PackingElement::FileBase);
700
701sub keyword() { "pkgconfig" }
702__PACKAGE__->register_with_factory;
703
704package OpenBSD::PackingElement::LibtoolLib;
705our @ISA=qw(OpenBSD::PackingElement::FileBase);
706
707sub keyword() { "ltlib" }
708__PACKAGE__->register_with_factory;
709
710package OpenBSD::PackingElement::Binary;
711our @ISA=qw(OpenBSD::PackingElement::FileBase);
712
713sub keyword() { "bin" }
714__PACKAGE__->register_with_factory;
715
716# Comment is very special
717package OpenBSD::PackingElement::Comment;
718our @ISA=qw(OpenBSD::PackingElement::Meta);
719
720sub keyword() { "comment" }
721__PACKAGE__->register_with_factory;
722
723sub destate
724{
725	my ($self, $state) = @_;
726	$self->{cwd} = $state->{cwd};
727}
728
729sub add
730{
731	my ($class, $plist, $args) = @_;
732
733	if ($args =~ m/^\$OpenBSD.*\$\s*$/o) {
734		return OpenBSD::PackingElement::CVSTag->add($plist, $args);
735	} elsif ($args =~ m/^(?:subdir|pkgpath)\=(.*?)\s+cdrom\=(.*?)\s+ftp\=(.*?)\s*$/o) {
736		return OpenBSD::PackingElement::ExtraInfo->add($plist, $1, $2, $3);
737	} elsif ($args eq 'no checksum') {
738		$plist->{state}->{nochecksum} = 1;
739		return;
740	} else {
741		return $class->SUPER::add($plist, $args);
742	}
743}
744
745package OpenBSD::PackingElement::CVSTag;
746our @ISA=qw(OpenBSD::PackingElement::Meta);
747
748sub keyword() { 'comment' }
749
750sub category() { 'cvstags'}
751
752# don't incorporate this into compared signatures
753sub write_without_variation
754{
755}
756
757package OpenBSD::PackingElement::sha;
758our @ISA=qw(OpenBSD::PackingElement::Annotation);
759
760__PACKAGE__->register_with_factory('sha');
761
762sub add
763{
764	my ($class, $plist, $args) = @_;
765
766	require OpenBSD::md5;
767
768	$plist->{state}->{lastchecksummable}->add_digest(OpenBSD::sha->fromstring($args));
769	return;
770}
771
772package OpenBSD::PackingElement::tag;
773our @ISA=qw(OpenBSD::PackingElement::Annotation);
774
775__PACKAGE__->register_with_factory('tag');
776
777sub add
778{
779	my ($class, $plist, $args) = @_;
780
781	if ($args eq 'no checksum') {
782		$plist->{state}{lastfile}{nochecksum} = 1;
783	} else {
784		my $object = $plist->{state}{lastfileobject};
785		$object->{tags}{$args} = 1;
786		push(@{$plist->{tags}{$args}}, $object);
787	}
788	return undef;
789}
790
791package OpenBSD::PackingElement::DefineTag;
792our @ISA=qw(OpenBSD::PackingElement::Meta);
793
794sub category() { 'define-tag' }
795sub keyword() { 'define-tag' }
796__PACKAGE__->register_with_factory;
797
798sub new
799{
800	my ($class, $args) = @_;
801	my ($tag, $condition, @command) = split(/\s+/, $args);
802	bless {
803		name => $tag,
804		when => $condition,
805		command => join(' ', @command)
806	}, $class;
807}
808
809sub stringize
810{
811	my $self = shift;
812	return join(' ', map { $self->{$_}}
813		(qw(name when command)));
814}
815
816package OpenBSD::PackingElement::symlink;
817our @ISA=qw(OpenBSD::PackingElement::Annotation);
818
819__PACKAGE__->register_with_factory('symlink');
820
821sub add
822{
823	my ($class, $plist, $args) = @_;
824
825	$plist->{state}->{lastfile}->make_symlink($args);
826	return;
827}
828
829package OpenBSD::PackingElement::hardlink;
830our @ISA=qw(OpenBSD::PackingElement::Annotation);
831
832__PACKAGE__->register_with_factory('link');
833
834sub add
835{
836	my ($class, $plist, $args) = @_;
837
838	$plist->{state}->{lastfile}->make_hardlink($args);
839	return;
840}
841
842package OpenBSD::PackingElement::temp;
843our @ISA=qw(OpenBSD::PackingElement::Annotation);
844
845__PACKAGE__->register_with_factory('temp');
846
847sub add
848{
849	my ($class, $plist, $args) = @_;
850	$plist->{state}->{lastfile}->set_tempname($args);
851	return;
852}
853
854package OpenBSD::PackingElement::size;
855our @ISA=qw(OpenBSD::PackingElement::Annotation);
856
857__PACKAGE__->register_with_factory('size');
858
859sub add
860{
861	my ($class, $plist, $args) = @_;
862
863	$plist->{state}->{lastfile}->add_size($args);
864	return;
865}
866
867package OpenBSD::PackingElement::ts;
868our @ISA=qw(OpenBSD::PackingElement::Annotation);
869
870__PACKAGE__->register_with_factory('ts');
871
872sub add
873{
874	my ($class, $plist, $args) = @_;
875
876	$plist->{state}->{lastfile}->add_timestamp($args);
877	return;
878}
879
880package OpenBSD::PackingElement::Option;
881our @ISA=qw(OpenBSD::PackingElement::Meta);
882
883sub keyword() { 'option' }
884__PACKAGE__->register_with_factory;
885
886sub new
887{
888	my ($class, $args) = @_;
889	if ($args eq 'no-default-conflict') {
890		return OpenBSD::PackingElement::NoDefaultConflict->new;
891	} elsif ($args eq 'manual-installation') {
892		return OpenBSD::PackingElement::ManualInstallation->new;
893	} elsif ($args eq 'firmware') {
894		return OpenBSD::PackingElement::Firmware->new;
895	} elsif ($args eq 'always-update') {
896		return OpenBSD::PackingElement::AlwaysUpdate->new;
897	} elsif ($args eq 'is-branch') {
898		return OpenBSD::PackingElement::IsBranch->new;
899	} else {
900		die "Unknown option: $args";
901	}
902}
903
904package OpenBSD::PackingElement::UniqueOption;
905our @ISA=qw(OpenBSD::PackingElement::Unique OpenBSD::PackingElement::Option);
906
907sub stringize
908{
909	my $self = shift;
910	return $self->category;
911}
912
913sub new
914{
915	my ($class, @args) = @_;
916	bless {}, $class;
917}
918
919package OpenBSD::PackingElement::NoDefaultConflict;
920our @ISA=qw(OpenBSD::PackingElement::UniqueOption);
921
922sub category() { 'no-default-conflict' }
923
924package OpenBSD::PackingElement::ManualInstallation;
925our @ISA=qw(OpenBSD::PackingElement::UniqueOption);
926
927sub category() { 'manual-installation' }
928
929# XXX don't incorporate this in signatures.
930sub write_no_sig()
931{
932}
933
934package OpenBSD::PackingElement::Firmware;
935our @ISA=qw(OpenBSD::PackingElement::ManualInstallation);
936sub category() { 'firmware' }
937
938package OpenBSD::PackingElement::AlwaysUpdate;
939our @ISA=qw(OpenBSD::PackingElement::UniqueOption);
940
941sub category()
942{
943	'always-update';
944}
945
946package OpenBSD::PackingElement::IsBranch;
947our @ISA=qw(OpenBSD::PackingElement::UniqueOption);
948
949sub category()
950{
951	'is-branch';
952}
953# The special elements that don't end in the right place
954package OpenBSD::PackingElement::ExtraInfo;
955our @ISA=qw(OpenBSD::PackingElement::Unique OpenBSD::PackingElement::Comment);
956
957sub category() { 'extrainfo' }
958
959sub new
960{
961	my ($class, $subdir, $cdrom, $ftp) = @_;
962
963	$cdrom =~ s/^\"(.*)\"$/$1/;
964	$cdrom =~ s/^\'(.*)\'$/$1/;
965	$ftp =~ s/^\"(.*)\"$/$1/;
966	$ftp =~ s/^\'(.*)\'$/$1/;
967	bless { subdir => $subdir,
968		path => OpenBSD::PkgPath->new($subdir),
969	    cdrom => $cdrom,
970	    ftp => $ftp}, $class;
971}
972
973sub subdir
974{
975	return shift->{subdir};
976}
977
978sub may_quote
979{
980	my $s = shift;
981	if ($s =~ m/\s/) {
982		return '"'.$s.'"';
983	} else {
984		return $s;
985	}
986}
987
988sub stringize
989{
990	my $self = shift;
991	return join(' ',
992	    "pkgpath=".$self->{subdir},
993	    "cdrom=".may_quote($self->{cdrom}),
994	    "ftp=".may_quote($self->{ftp}));
995}
996
997package OpenBSD::PackingElement::Name;
998use File::Spec;
999our @ISA=qw(OpenBSD::PackingElement::Unique);
1000
1001sub keyword() { "name" }
1002__PACKAGE__->register_with_factory;
1003sub category() { "name" }
1004
1005package OpenBSD::PackingElement::LocalBase;
1006our @ISA=qw(OpenBSD::PackingElement::Unique);
1007
1008sub keyword() { "localbase" }
1009__PACKAGE__->register_with_factory;
1010sub category() { "localbase" }
1011
1012package OpenBSD::PackingElement::Url;
1013our @ISA=qw(OpenBSD::PackingElement::Unique);
1014
1015sub keyword() { "url" }
1016__PACKAGE__->register_with_factory;
1017sub category() { "url" }
1018
1019# XXX don't incorporate this in signatures.
1020sub write_no_sig()
1021{
1022}
1023
1024package OpenBSD::PackingElement::Conflict;
1025our @ISA=qw(OpenBSD::PackingElement::Meta);
1026
1027sub keyword() { "conflict" }
1028__PACKAGE__->register_with_factory;
1029sub category() { "conflict" }
1030
1031sub spec
1032{
1033	my $self =shift;
1034
1035	require OpenBSD::Search;
1036	return OpenBSD::Search::PkgSpec->new($self->name);
1037}
1038
1039package OpenBSD::PackingElement::Dependency;
1040our @ISA=qw(OpenBSD::PackingElement::Depend);
1041use OpenBSD::Error;
1042
1043sub keyword() { "depend" }
1044__PACKAGE__->register_with_factory;
1045sub category() { "depend" }
1046
1047sub new
1048{
1049	my ($class, $args) = @_;
1050	my ($pkgpath, $pattern, $def) = split /\:/o, $args;
1051	bless { name => $def, pkgpath => $pkgpath, pattern => $pattern,
1052	    def => $def }, $class;
1053}
1054
1055sub stringize
1056{
1057	my $self = shift;
1058	return join(':', map { $self->{$_}}
1059	    (qw(pkgpath pattern def)));
1060}
1061
1062OpenBSD::Auto::cache(spec,
1063    sub {
1064	require OpenBSD::Search;
1065
1066	my $self = shift;
1067	return OpenBSD::Search::PkgSpec->new($self->{pattern})
1068	    ->add_pkgpath_hint($self->{pkgpath});
1069    });
1070
1071package OpenBSD::PackingElement::Wantlib;
1072our @ISA=qw(OpenBSD::PackingElement::Depend);
1073
1074sub category() { "wantlib" }
1075sub keyword() { "wantlib" }
1076__PACKAGE__->register_with_factory;
1077
1078OpenBSD::Auto::cache(spec,
1079    sub {
1080    	my $self = shift;
1081
1082    	require OpenBSD::LibSpec;
1083	return OpenBSD::LibSpec->from_string($self->name);
1084    });
1085package OpenBSD::PackingElement::PkgPath;
1086our @ISA=qw(OpenBSD::PackingElement::Meta);
1087
1088sub keyword() { "pkgpath" }
1089__PACKAGE__->register_with_factory;
1090sub category() { "pkgpath" }
1091
1092sub new
1093{
1094	my ($class, $fullpkgpath) = @_;
1095	bless {name => $fullpkgpath,
1096	    path => OpenBSD::PkgPath::WithOpts->new($fullpkgpath)}, $class;
1097}
1098
1099sub subdir
1100{
1101	return shift->{name};
1102}
1103
1104package OpenBSD::PackingElement::AskUpdate;
1105our @ISA=qw(OpenBSD::PackingElement::Meta);
1106
1107sub new
1108{
1109	my ($class, $args) = @_;
1110	my ($pattern, $message) = split /\s+/o, $args, 2;
1111	bless { pattern => $pattern, message => $message}, $class;
1112}
1113
1114sub stringize
1115{
1116	my $self = shift;
1117	return join(' ', map { $self->{$_}}
1118	    (qw(pattern message)));
1119}
1120
1121sub keyword() { "ask-update" }
1122__PACKAGE__->register_with_factory;
1123sub category() { "ask-update" }
1124
1125OpenBSD::Auto::cache(spec,
1126    sub {
1127	require OpenBSD::PkgSpec;
1128
1129	my $self = shift;
1130	return OpenBSD::PkgSpec->new($self->{pattern})
1131    });
1132
1133package OpenBSD::PackingElement::NewAuth;
1134our @ISA=qw(OpenBSD::PackingElement::Action);
1135
1136package OpenBSD::PackingElement::NewUser;
1137our @ISA=qw(OpenBSD::PackingElement::NewAuth);
1138
1139sub type() { "user" }
1140sub category() { "users" }
1141sub keyword() { "newuser" }
1142__PACKAGE__->register_with_factory;
1143
1144sub new
1145{
1146	my ($class, $args) = @_;
1147	my ($name, $uid, $group, $loginclass, $comment, $home, $shell) =
1148	    split /\:/o, $args;
1149	bless { name => $name, uid => $uid, group => $group,
1150	    class => $loginclass,
1151	    comment => $comment, home => $home, shell => $shell }, $class;
1152}
1153
1154sub destate
1155{
1156	my ($self, $state) = @_;
1157	my $uid = $self->{uid};
1158	$uid =~ s/^\!//;
1159	$state->{owners}{$self->{name}} = $uid;
1160}
1161
1162sub check
1163{
1164	my $self = shift;
1165	my ($name, $passwd, $uid, $gid, $quota, $class, $gcos, $dir, $shell,
1166	    $expire) = getpwnam($self->name);
1167	return unless defined $name;
1168	if ($self->{uid} =~ m/^\!(.*)$/o) {
1169		return 0 unless $uid == $1;
1170	}
1171	if ($self->{group} =~ m/^\!(.*)$/o) {
1172		my $g = $1;
1173		unless ($g =~ m/^\d+$/o) {
1174			$g = getgrnam($g);
1175			return 0 unless defined $g;
1176		}
1177		return 0 unless $gid eq $g;
1178	}
1179	if ($self->{class} =~ m/^\!(.*)$/o) {
1180		return 0 unless $class eq $1;
1181	}
1182	if ($self->{comment} =~ m/^\!(.*)$/o) {
1183		return 0 unless $gcos eq $1;
1184	}
1185	if ($self->{home} =~ m/^\!(.*)$/o) {
1186		return 0 unless $dir eq $1;
1187	}
1188	if ($self->{shell} =~ m/^\!(.*)$/o) {
1189		return 0 unless $shell eq $1;
1190	}
1191	return 1;
1192}
1193
1194sub stringize
1195{
1196	my $self = shift;
1197	return join(':', map { $self->{$_}}
1198	    (qw(name uid group class comment home shell)));
1199}
1200
1201package OpenBSD::PackingElement::NewGroup;
1202our @ISA=qw(OpenBSD::PackingElement::NewAuth);
1203
1204
1205sub type() { "group" }
1206sub category() { "groups" }
1207sub keyword() { "newgroup" }
1208__PACKAGE__->register_with_factory;
1209
1210sub new
1211{
1212	my ($class, $args) = @_;
1213	my ($name, $gid) = split /\:/o, $args;
1214	bless { name => $name, gid => $gid }, $class;
1215}
1216
1217sub destate
1218{
1219	my ($self, $state) = @_;
1220	my $gid = $self->{gid};
1221	$gid =~ s/^\!//;
1222	$state->{groups}{$self->{name}} = $gid;
1223}
1224
1225sub check
1226{
1227	my $self = shift;
1228	my ($name, $passwd, $gid, $members) = getgrnam($self->name);
1229	return unless defined $name;
1230	if ($self->{gid} =~ m/^\!(.*)$/o) {
1231		return 0 unless $gid == $1;
1232	}
1233	return 1;
1234}
1235
1236sub stringize($)
1237{
1238	my $self = $_[0];
1239	return join(':', map { $self->{$_}}
1240	    (qw(name gid)));
1241}
1242
1243package OpenBSD::PackingElement::Cwd;
1244use File::Spec;
1245our @ISA=qw(OpenBSD::PackingElement::State);
1246
1247
1248sub keyword() { 'cwd' }
1249__PACKAGE__->register_with_factory;
1250
1251sub destate
1252{
1253	my ($self, $state) = @_;
1254	$state->set_cwd($self->name);
1255}
1256
1257package OpenBSD::PackingElement::Owner;
1258our @ISA=qw(OpenBSD::PackingElement::State);
1259
1260sub keyword() { 'owner' }
1261__PACKAGE__->register_with_factory;
1262
1263sub destate
1264{
1265	my ($self, $state) = @_;
1266
1267	delete $state->{uid};
1268	if ($self->name eq '') {
1269		undef $state->{owner};
1270	} else {
1271		$state->{owner} = $self->name;
1272		if (defined $state->{owners}{$self->name}) {
1273			$state->{uid} = $state->{owners}{$self->name};
1274		}
1275	}
1276}
1277
1278package OpenBSD::PackingElement::Group;
1279our @ISA=qw(OpenBSD::PackingElement::State);
1280
1281sub keyword() { 'group' }
1282__PACKAGE__->register_with_factory;
1283
1284sub destate
1285{
1286	my ($self, $state) = @_;
1287
1288	delete $state->{gid};
1289	if ($self->name eq '') {
1290		undef $state->{group};
1291	} else {
1292		$state->{group} = $self->name;
1293		if (defined $state->{groups}{$self->name}) {
1294			$state->{gid} = $state->{groups}{$self->name};
1295		}
1296	}
1297}
1298
1299package OpenBSD::PackingElement::Mode;
1300our @ISA=qw(OpenBSD::PackingElement::State);
1301
1302sub keyword() { 'mode' }
1303__PACKAGE__->register_with_factory;
1304
1305sub destate
1306{
1307	my ($self, $state) = @_;
1308
1309	if ($self->name eq '') {
1310		undef $state->{mode};
1311	} else {
1312		$state->{mode} = $self->name;
1313	}
1314}
1315
1316package OpenBSD::PackingElement::Sysctl;
1317our @ISA=qw(OpenBSD::PackingElement::Action);
1318
1319sub keyword() { 'sysctl' }
1320__PACKAGE__->register_with_factory;
1321
1322sub new
1323
1324{
1325	my ($class, $args) = @_;
1326	if ($args =~ m/^\s*(.*)\s*(\=|\>=)\s*(.*)\s*$/o) {
1327		bless { name => $1, mode => $2, value => $3}, $class;
1328	} else {
1329		die "Bad syntax for \@sysctl";
1330	}
1331}
1332
1333sub stringize
1334{
1335	my $self = shift;
1336	return $self->{name}.$self->{mode}.$self->{value};
1337}
1338
1339package OpenBSD::PackingElement::ExeclikeAction;
1340use File::Basename;
1341use OpenBSD::Error;
1342our @ISA=qw(OpenBSD::PackingElement::Action);
1343
1344sub expand
1345{
1346	my ($self, $state) = @_;
1347	my $e = $self->name;
1348	if ($e =~ m/\%F/o) {
1349		die "Bad expand" unless defined $state->{lastfile};
1350		$e =~ s/\%F/$state->{lastfile}->{name}/g;
1351	}
1352	if ($e =~ m/\%D/o) {
1353		die "Bad expand" unless defined $state->{cwd};
1354		$e =~ s/\%D/$state->cwd/ge;
1355	}
1356	if ($e =~ m/\%B/o) {
1357		die "Bad expand" unless defined $state->{lastfile};
1358		$e =~ s/\%B/dirname($state->{lastfile}->fullname)/ge;
1359	}
1360	if ($e =~ m/\%f/o) {
1361		die "Bad expand" unless defined $state->{lastfile};
1362		$e =~ s/\%f/basename($state->{lastfile}->fullname)/ge;
1363	}
1364	return $e;
1365}
1366
1367sub destate
1368{
1369	my ($self, $state) = @_;
1370	$self->{expanded} = $self->expand($state);
1371}
1372
1373sub run
1374{
1375	my ($self, $state) = @_;
1376
1377	$state->ldconfig->ensure;
1378	$state->say("#1 #2", $self->keyword, $self->{expanded})
1379	    if $state->verbose >= 2;
1380	$state->log->system(OpenBSD::Paths->sh, '-c', $self->{expanded})
1381	    unless $state->{not};
1382}
1383
1384package OpenBSD::PackingElement::Exec;
1385our @ISA=qw(OpenBSD::PackingElement::ExeclikeAction);
1386
1387sub keyword() { "exec" }
1388__PACKAGE__->register_with_factory;
1389
1390package OpenBSD::PackingElement::ExecAlways;
1391our @ISA=qw(OpenBSD::PackingElement::Exec);
1392
1393sub keyword() { "exec-always" }
1394__PACKAGE__->register_with_factory;
1395
1396package OpenBSD::PackingElement::ExecAdd;
1397our @ISA=qw(OpenBSD::PackingElement::Exec);
1398
1399sub keyword() { "exec-add" }
1400__PACKAGE__->register_with_factory;
1401
1402package OpenBSD::PackingElement::ExecUpdate;
1403our @ISA=qw(OpenBSD::PackingElement::Exec);
1404
1405sub keyword() { "exec-update" }
1406__PACKAGE__->register_with_factory;
1407
1408package OpenBSD::PackingElement::Unexec;
1409our @ISA=qw(OpenBSD::PackingElement::ExeclikeAction);
1410
1411sub keyword() { "unexec" }
1412__PACKAGE__->register_with_factory;
1413
1414package OpenBSD::PackingElement::UnexecAlways;
1415our @ISA=qw(OpenBSD::PackingElement::Unexec);
1416
1417sub keyword() { "unexec-always" }
1418__PACKAGE__->register_with_factory;
1419
1420package OpenBSD::PackingElement::UnexecUpdate;
1421our @ISA=qw(OpenBSD::PackingElement::Unexec);
1422
1423sub keyword() { "unexec-update" }
1424__PACKAGE__->register_with_factory;
1425
1426package OpenBSD::PackingElement::UnexecDelete;
1427our @ISA=qw(OpenBSD::PackingElement::Unexec);
1428
1429sub keyword() { "unexec-delete" }
1430__PACKAGE__->register_with_factory;
1431
1432package OpenBSD::PackingElement::ExtraUnexec;
1433our @ISA=qw(OpenBSD::PackingElement::ExeclikeAction);
1434
1435sub keyword() { "extraunexec" }
1436__PACKAGE__->register_with_factory;
1437
1438package OpenBSD::PackingElement::DirlikeObject;
1439our @ISA=qw(OpenBSD::PackingElement::FileObject);
1440
1441package OpenBSD::PackingElement::DirBase;
1442our @ISA=qw(OpenBSD::PackingElement::DirlikeObject);
1443
1444sub destate
1445{
1446	my ($self, $state) = @_;
1447	$state->{lastdir} = $self;
1448	$self->SUPER::destate($state);
1449}
1450
1451
1452sub stringize
1453{
1454	my $self = shift;
1455	return $self->name."/";
1456}
1457
1458sub write
1459{
1460	my ($self, $fh) = @_;
1461	$self->SUPER::write($fh);
1462}
1463
1464package OpenBSD::PackingElement::Dir;
1465our @ISA=qw(OpenBSD::PackingElement::DirBase);
1466
1467sub keyword() { "dir" }
1468__PACKAGE__->register_with_factory;
1469
1470sub destate
1471{
1472	my ($self, $state) = @_;
1473	$self->SUPER::destate($state);
1474	$self->compute_modes($state);
1475}
1476
1477sub needs_keyword
1478{
1479	my $self = shift;
1480	return $self->stringize =~ m/\^@/o;
1481}
1482
1483package OpenBSD::PackingElement::Infodir;
1484our @ISA=qw(OpenBSD::PackingElement::Dir);
1485sub keyword() { "info" }
1486sub needs_keyword() { 1 }
1487
1488package OpenBSD::PackingElement::Fontdir;
1489our @ISA=qw(OpenBSD::PackingElement::Dir);
1490sub keyword() { "fontdir" }
1491__PACKAGE__->register_with_factory;
1492sub needs_keyword() { 1 }
1493sub dirclass() { "OpenBSD::PackingElement::Fontdir" }
1494
1495sub install
1496{
1497	my ($self, $state) = @_;
1498	$self->SUPER::install($state);
1499	$state->log("You may wish to update your font path for #1", $self->fullname);
1500	$state->{recorder}{fonts_todo}{$state->{destdir}.$self->fullname} = 1;
1501}
1502
1503sub reload
1504{
1505	my ($self, $state) = @_;
1506	$state->{recorder}{fonts_todo}{$state->{destdir}.$self->fullname} = 1;
1507}
1508
1509sub update_fontalias
1510{
1511	my $dirname = shift;
1512	my @aliases;
1513
1514	if (-d "$dirname") {
1515		for my $alias (glob "$dirname/fonts.alias-*") {
1516			open my $f ,'<', $alias or next;
1517			push(@aliases, <$f>);
1518			close $f;
1519		}
1520		open my $f, '>', "$dirname/fonts.alias";
1521		print $f @aliases;
1522		close $f;
1523	}
1524}
1525
1526sub restore_fontdir
1527{
1528	my ($dirname, $state) = @_;
1529	if (-f "$dirname/fonts.dir.dist") {
1530
1531		unlink("$dirname/fonts.dir");
1532		$state->copy_file("$dirname/fonts.dir.dist",
1533		    "$dirname/fonts.dir");
1534	}
1535}
1536
1537sub run_if_exists
1538{
1539	my ($state, $cmd, @l) = @_;
1540
1541	if (-x $cmd) {
1542		$state->vsystem($cmd, @l);
1543	} else {
1544		$state->errsay("#1 not found", $cmd);
1545	}
1546}
1547
1548sub finish
1549{
1550	my ($class, $state) = @_;
1551	my @l = keys %{$state->{recorder}->{fonts_todo}};
1552
1553	if (@l != 0) {
1554		require OpenBSD::Error;
1555
1556		return if $state->{not};
1557		map { update_fontalias($_) } @l;
1558		if (-d "@l") {
1559			run_if_exists($state, OpenBSD::Paths->mkfontscale, '--', @l);
1560			run_if_exists($state, OpenBSD::Paths->mkfontdir, '--', @l);
1561			map { restore_fontdir($_, $state) } @l;
1562		}
1563
1564		run_if_exists($state, OpenBSD::Paths->fc_cache, '--', @l);
1565	}
1566}
1567
1568
1569package OpenBSD::PackingElement::Mandir;
1570our @ISA=qw(OpenBSD::PackingElement::Dir);
1571
1572sub keyword() { "mandir" }
1573__PACKAGE__->register_with_factory;
1574sub needs_keyword() { 1 }
1575sub dirclass() { "OpenBSD::PackingElement::Mandir" }
1576
1577package OpenBSD::PackingElement::Extra;
1578our @ISA=qw(OpenBSD::PackingElement::FileObject);
1579
1580sub keyword() { 'extra' }
1581sub absolute_okay() { 1 }
1582__PACKAGE__->register_with_factory;
1583
1584sub destate
1585{
1586	my ($self, $state) = @_;
1587	$self->compute_fullname($state);
1588}
1589
1590sub dirclass() { "OpenBSD::PackingElement::Extradir" }
1591
1592package OpenBSD::PackingElement::Extradir;
1593our @ISA=qw(OpenBSD::PackingElement::DirBase OpenBSD::PackingElement::Extra);
1594sub absolute_okay() { 1 }
1595
1596sub destate
1597{
1598	&OpenBSD::PackingElement::Extra::destate;
1599}
1600
1601package OpenBSD::PackingElement::SpecialFile;
1602our @ISA=qw(OpenBSD::PackingElement::Unique);
1603
1604sub add_digest
1605{
1606	&OpenBSD::PackingElement::FileBase::add_digest;
1607}
1608
1609sub add_size
1610{
1611	&OpenBSD::PackingElement::FileBase::add_size;
1612}
1613
1614sub add_timestamp
1615{
1616	# just don't
1617}
1618
1619sub compute_digest
1620{
1621	&OpenBSD::PackingElement::FileObject::compute_digest;
1622}
1623
1624sub write
1625{
1626	&OpenBSD::PackingElement::FileBase::write;
1627}
1628
1629sub needs_keyword { 0 }
1630
1631sub add_object
1632{
1633	my ($self, $plist) = @_;
1634	$self->{infodir} = $plist->{infodir};
1635	$self->SUPER::add_object($plist);
1636}
1637
1638sub infodir
1639{
1640	my $self = shift;
1641	return ${$self->{infodir}};
1642}
1643
1644sub stringize
1645{
1646	my $self = shift;
1647	return $self->category;
1648}
1649
1650sub fullname
1651{
1652	my $self = shift;
1653	my $d = $self->infodir;
1654	if (defined $d) {
1655		return $d.$self->name;
1656	} else {
1657		return undef;
1658	}
1659}
1660
1661sub category
1662{
1663	my $self = shift;
1664
1665	return $self->name;
1666}
1667
1668sub new
1669{
1670	&OpenBSD::PackingElement::UniqueOption::new;
1671}
1672
1673sub may_verify_digest
1674{
1675	my ($self, $state) = @_;
1676	if (!$state->{check_digest}) {
1677		return;
1678	}
1679	if (!defined $self->{d}) {
1680		$state->log->fatal($state->f("#1 does not have a signature",
1681		    $self->fullname));
1682	}
1683	my $d = $self->compute_digest($self->fullname);
1684	if (!$d->equals($self->{d})) {
1685		$state->log->fatal($state->f("checksum for #1 does not match",
1686		    $self->fullname));
1687	}
1688	if ($state->verbose >= 3) {
1689		$state->say("Checksum match for #1", $self->fullname);
1690	}
1691}
1692
1693package OpenBSD::PackingElement::FCONTENTS;
1694our @ISA=qw(OpenBSD::PackingElement::SpecialFile);
1695sub name() { OpenBSD::PackageInfo::CONTENTS }
1696# XXX we don't write `self'
1697sub write
1698{}
1699
1700sub copy_shallow_if
1701{
1702}
1703
1704sub copy_deep_if
1705{
1706}
1707
1708# CONTENTS doesn't have a checksum
1709sub may_verify_digest
1710{
1711}
1712
1713package OpenBSD::PackingElement::FDESC;
1714our @ISA=qw(OpenBSD::PackingElement::SpecialFile);
1715sub name() { OpenBSD::PackageInfo::DESC }
1716
1717package OpenBSD::PackingElement::DisplayFile;
1718our @ISA=qw(OpenBSD::PackingElement::SpecialFile);
1719use OpenBSD::Error;
1720
1721sub prepare
1722{
1723	my ($self, $state) = @_;
1724	my $fname = $self->fullname;
1725	if (open(my $src, '<', $fname)) {
1726		while (<$src>) {
1727			chomp;
1728			next if m/^\+\-+\s*$/o;
1729			s/^[+-] //o;
1730			$state->log("#1", $_);
1731		}
1732	} else {
1733		$state->errsay("Can't open #1: #2", $fname, $!);
1734    	}
1735}
1736
1737package OpenBSD::PackingElement::FDISPLAY;
1738our @ISA=qw(OpenBSD::PackingElement::DisplayFile);
1739sub name() { OpenBSD::PackageInfo::DISPLAY }
1740
1741package OpenBSD::PackingElement::FUNDISPLAY;
1742our @ISA=qw(OpenBSD::PackingElement::DisplayFile);
1743sub name() { OpenBSD::PackageInfo::UNDISPLAY }
1744
1745package OpenBSD::PackingElement::Arch;
1746our @ISA=qw(OpenBSD::PackingElement::Unique);
1747
1748sub category() { 'arch' }
1749sub keyword() { 'arch' }
1750__PACKAGE__->register_with_factory;
1751
1752sub new
1753{
1754	my ($class, $args) = @_;
1755	my @arches= split(/\,/o, $args);
1756	bless { arches => \@arches }, $class;
1757}
1758
1759sub stringize($)
1760{
1761	my $self = $_[0];
1762	return join(',', @{$self->{arches}});
1763}
1764
1765sub check
1766{
1767	my ($self, $forced_arch) = @_;
1768
1769	for my $ok (@{$self->{arches}}) {
1770		return 1 if $ok eq '*';
1771		if (defined $forced_arch) {
1772			if ($ok eq $forced_arch) {
1773				return 1;
1774			} else {
1775				next;
1776			}
1777		}
1778		return 1 if $ok eq OpenBSD::Paths->machine_architecture;
1779		return 1 if $ok eq OpenBSD::Paths->architecture;
1780	}
1781	return;
1782}
1783
1784package OpenBSD::PackingElement::Signer;
1785our @ISA=qw(OpenBSD::PackingElement::Unique);
1786sub keyword() { 'signer' }
1787__PACKAGE__->register_with_factory;
1788sub category() { "signer" }
1789sub new
1790{
1791	my ($class, $args) = @_;
1792	unless ($args =~ m/^[\w\d\.\-\+\@]+$/) {
1793		die "Invalid characters in signer $args\n";
1794	}
1795	$class->SUPER::new($args);
1796}
1797
1798# don't incorporate this into compared signatures
1799sub write_without_variation
1800{
1801}
1802
1803# XXX digital-signatures have to be unique, since they are a part
1804# of the unsigned packing-list, with only the b64sig part removed
1805# (likewise for signer)
1806package OpenBSD::PackingElement::DigitalSignature;
1807our @ISA=qw(OpenBSD::PackingElement::Unique);
1808
1809sub keyword() { 'digital-signature' }
1810__PACKAGE__->register_with_factory;
1811sub category() { "digital-signature" }
1812
1813# parse to and from a subset of iso8601
1814#
1815# allows us to represent timestamps in a human readable format without
1816# any ambiguity
1817sub time_to_iso8601
1818{
1819	my $time = shift;
1820	my ($sec, $min, $hour, $day, $month, $year, @rest) = gmtime($time);
1821	return sprintf("%04d-%02d-%02dT%02d:%02d:%02dZ",
1822	    $year+1900, $month+1, $day, $hour, $min, $sec);
1823}
1824
1825sub iso8601
1826{
1827	my $self = shift;
1828	return time_to_iso8601($self->{timestamp});
1829}
1830
1831sub iso8601_to_time
1832{
1833	if ($_[0] =~ m/^(\d{4})\-(\d{2})\-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})Z$/) {
1834		my ($year, $month, $day, $hour, $min, $sec) =
1835			($1 - 1900, $2-1, $3, $4, $5, $6);
1836		require POSIX;
1837		my $oldtz = $ENV{TZ};
1838		$ENV{TZ} = 'UTC';
1839		my $t = POSIX::mktime($sec, $min, $hour, $day, $month, $year);
1840		if (defined $oldtz) {
1841			$ENV{TZ} = $oldtz;
1842		} else {
1843			delete $ENV{TZ};
1844		}
1845		return $t;
1846	} else {
1847		die "Incorrect ISO8601 timestamp: $_[0]";
1848	}
1849}
1850
1851sub new
1852{
1853	my ($class, $args) = @_;
1854	my ($key, $tsbase, $tsmin, $tssec, $signature) = split(/\:/, $args);
1855	my $timestamp = iso8601_to_time("$tsbase:$tsmin:$tssec");
1856	bless { key => $key, timestamp => $timestamp, b64sig => $signature },
1857		$class;
1858}
1859
1860sub blank
1861{
1862	my ($class, $type) = @_;
1863	bless { key => $type, timestamp => time, b64sig => '' }, $class;
1864}
1865
1866sub stringize
1867{
1868	my $self = shift;
1869	return join(':', $self->{key}, time_to_iso8601($self->{timestamp}),
1870	    $self->{b64sig});
1871}
1872
1873sub write_no_sig
1874{
1875	my ($self, $fh) = @_;
1876	print $fh "\@", $self->keyword, " ", $self->{key}, ":",
1877	    time_to_iso8601($self->{timestamp}), "\n";
1878}
1879
1880# don't incorporate this into compared signatures
1881sub write_without_variation
1882{
1883}
1884
1885package OpenBSD::PackingElement::Old;
1886our @ISA=qw(OpenBSD::PackingElement);
1887
1888my $warned;
1889
1890sub new
1891{
1892	my ($class, $k, $args) = @_;
1893	bless { keyword => $k, name => $args }, $class;
1894}
1895
1896sub add
1897{
1898	my ($o, $plist, $args) = @_;
1899	my $keyword = $$o;
1900	if (!$warned->{$keyword}) {
1901		print STDERR "Warning: obsolete construct: \@$keyword $args\n";
1902		$warned->{$keyword} = 1;
1903	}
1904	my $o2 = OpenBSD::PackingElement::Old->new($keyword, $args);
1905	$o2->add_object($plist);
1906	$plist->{deprecated} = 1;
1907	return undef;
1908}
1909
1910sub keyword
1911{
1912	my $self = shift;
1913	return $self->{keyword};
1914}
1915
1916sub register_old_keyword
1917{
1918	my ($class, $k) = @_;
1919	$class->register_with_factory($k, bless \$k, $class);
1920}
1921
1922for my $k (qw(src display mtree ignore_inst dirrm pkgcfl pkgdep newdepend
1923    libdepend endfake ignore vendor incompatibility md5)) {
1924	__PACKAGE__->register_old_keyword($k);
1925}
1926
1927# Real pkgpath objects, with matching properties
1928package OpenBSD::PkgPath;
1929sub new
1930{
1931	my ($class, $fullpkgpath) = @_;
1932	my ($dir, @mandatory) = split(/\,/, $fullpkgpath);
1933	return bless {dir => $dir,
1934		mandatory => {map {($_, 1)} @mandatory},
1935	}, $class;
1936}
1937
1938sub fullpkgpath
1939{
1940	my ($self) = @_;
1941	if(%{$self->{mandatory}}) {
1942		my $m = join(",", keys %{$self->{mandatory}});
1943		return "$self->{dir},$m";
1944	} else {
1945		return $self->{dir};
1946	}
1947}
1948
1949# a pkgpath has a dir, and some flavors/multi parts. To match, we must
1950# remove them all. So, keep a full hash of everything we have (has), and
1951# when stuff $to_rm matches, remove them from $from.
1952# We match when we're left with nothing.
1953sub trim
1954{
1955	my ($self, $has, $from, $to_rm) = @_;
1956	for my $f (keys %$to_rm) {
1957		if ($has->{$f}) {
1958			delete $from->{$f};
1959		} else {
1960			return 0;
1961		}
1962	}
1963	return 1;
1964}
1965
1966# basic match: after mandatory, nothing left
1967sub match2
1968{
1969	my ($self, $has, $h) = @_;
1970	if (keys %$h) {
1971		return 0;
1972	} else {
1973		return 1;
1974	}
1975}
1976
1977# zap mandatory, check that what's left is okay.
1978sub match
1979{
1980	my ($self, $other) = @_;
1981	# make a copy of options
1982	my %h = %{$other->{mandatory}};
1983	if (!$self->trim($other->{mandatory}, \%h, $self->{mandatory})) {
1984		return 0;
1985	}
1986	if ($self->match2($other->{mandatory}, \%h)) {
1987		return 1;
1988	} else {
1989		return 0;
1990	}
1991}
1992
1993package OpenBSD::PkgPath::WithOpts;
1994our @ISA = qw(OpenBSD::PkgPath);
1995
1996sub new
1997{
1998	my ($class, $fullpkgpath) = @_;
1999	my @opts = ();
2000	while ($fullpkgpath =~ s/\[\,(.*?)\]//) {
2001		push(@opts, {map {($_, 1)} split(/\,/, $1) });
2002	};
2003	my $o = $class->SUPER::new($fullpkgpath);
2004	if (@opts == 0) {
2005		bless $o, "OpenBSD::PkgPath";
2006	} else {
2007		$o->{opts} = \@opts;
2008	}
2009	return $o;
2010}
2011
2012# match with options: systematically trim any optional part that  fully
2013# matches, until we're left with nothing, or some options keep happening.
2014sub match2
2015{
2016	my ($self, $has, $h) = @_;
2017	if (!keys %$h) {
2018		return 1;
2019	}
2020	for my $opts (@{$self->{opts}}) {
2021		my %h2 = %$h;
2022		if ($self->trim($has, \%h2, $opts)) {
2023			$h = \%h2;
2024			if (!keys %$h) {
2025				return 1;
2026			}
2027		}
2028	}
2029	return 0;
2030}
2031
20321;
2033