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