xref: /openbsd-src/usr.sbin/pkg_add/OpenBSD/PkgCreate.pm (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1#! /usr/bin/perl
2# ex:ts=8 sw=4:
3# $OpenBSD: PkgCreate.pm,v 1.122 2016/09/06 10:41:51 espie Exp $
4#
5# Copyright (c) 2003-2014 Marc Espie <espie@openbsd.org>
6#
7# Permission to use, copy, modify, and distribute this software for any
8# purpose with or without fee is hereby granted, provided that the above
9# copyright notice and this permission notice appear in all copies.
10#
11# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18
19use strict;
20use warnings;
21
22use OpenBSD::AddCreateDelete;
23use OpenBSD::Dependencies;
24use OpenBSD::SharedLibs;
25use OpenBSD::Signer;
26
27package OpenBSD::PkgCreate::State;
28our @ISA = qw(OpenBSD::CreateSign::State);
29
30sub init
31{
32	my $self = shift;
33
34	$self->{stash} = {};
35	$self->SUPER::init(@_);
36	$self->{simple_status} = 0;
37}
38
39sub stash
40{
41	my ($self, $key) = @_;
42	return $self->{stash}{$key};
43}
44
45sub error
46{
47	my $self = shift;
48	my $msg = shift;
49	$self->{bad}++;
50	$self->progress->disable;
51	$self->errsay("Error: $msg", @_);
52}
53
54sub set_status
55{
56	my ($self, $status) = @_;
57	if ($self->{simple_status}) {
58		print "\n$status";
59	} else {
60		if ($self->progress->set_header($status)) {
61			$self->progress->message('');
62		} else {
63			$| = 1;
64			print "$status...";
65			$self->{simple_status} = 1;
66		}
67	}
68}
69
70sub end_status
71{
72	my $self = shift;
73
74	if ($self->{simple_status}) {
75		print "\n";
76	} else {
77		$self->progress->clear;
78	}
79}
80
81sub handle_options
82{
83	my $state = shift;
84
85	$state->{opt} = {
86	    'f' =>
87		    sub {
88			    push(@{$state->{contents}}, shift);
89		    },
90	    'p' =>
91		    sub {
92			    $state->{prefix} = shift;
93		    },
94	    'P' => sub {
95			    my $d = shift;
96			    $state->{dependencies}{$d} = 1;
97		    },
98	    'W' => sub {
99			    my $w = shift;
100			    $state->{wantlib}{$w} = 1;
101		    },
102	};
103	$state->{no_exports} = 1;
104	$state->SUPER::handle_options('p:f:d:M:U:A:B:P:W:qQ',
105	    '[-nQqvx] [-A arches] [-B pkg-destdir] [-D name[=value]]',
106	    '[-L localbase] [-M displayfile] [-P pkg-dependency]',
107	    '[-U undisplayfile] [-W wantedlib]',
108	    '[-d desc -D COMMENT=value -f packinglist -p prefix]',
109	    'pkg-name');
110
111	my $base = '/';
112	if (defined $state->opt('B')) {
113		$base = $state->opt('B');
114	}
115
116	$state->{base} = $base;
117
118}
119
120package OpenBSD::PkgCreate;
121
122use OpenBSD::PackingList;
123use OpenBSD::PackageInfo;
124use OpenBSD::Getopt;
125use OpenBSD::Temp;
126use OpenBSD::Error;
127use OpenBSD::Ustar;
128use OpenBSD::ArcCheck;
129use OpenBSD::Paths;
130use File::Basename;
131
132# Extra stuff needed to archive files
133package OpenBSD::PackingElement;
134sub create_package
135{
136	my ($self, $state) = @_;
137
138	$self->archive($state);
139	if ($state->verbose) {
140		$self->comment_create_package($state);
141	}
142}
143
144sub pretend_to_archive
145{
146	my ($self, $state) = @_;
147	$self->comment_create_package($state);
148}
149
150sub record_digest {}
151sub archive {}
152sub really_archived { 0 }
153sub comment_create_package {}
154sub grab_manpages {}
155
156sub print_file {}
157
158sub avert_duplicates_and_other_checks
159{
160	my ($self, $state) = @_;
161	return unless $self->NoDuplicateNames;
162	my $n = $self->fullname;
163	if (defined $state->stash($n)) {
164		$state->error("duplicate item in packing-list #1", $n);
165	}
166	$state->{stash}{$n} = 1;
167}
168
169sub makesum_plist
170{
171	my ($self, $state, $plist) = @_;
172	$self->add_object($plist);
173}
174
175sub verify_checksum
176{
177}
178
179sub register_forbidden
180{
181	my ($self, $state) = @_;
182	if ($self->is_forbidden) {
183		push(@{$state->{forbidden}}, $self);
184	}
185}
186
187sub is_forbidden() { 0 }
188sub resolve_link
189{
190	my ($filename, $base, $level) = @_;
191	$level //= 0;
192	if (-l $filename) {
193		my $l = readlink($filename);
194		if ($level++ > 14) {
195			return undef;
196		}
197		if ($l =~ m|^/|) {
198			return $base.resolve_link($l, $base, $level);
199		} else {
200			return resolve_link(File::Spec->catfile(File::Basename::dirname($filename),$l), $base, $level);
201		}
202	} else {
203		return $filename;
204	}
205}
206
207sub compute_checksum
208{
209	my ($self, $result, $state, $base) = @_;
210	my $name = $self->fullname;
211	my $fname = $name;
212	if (defined $base) {
213		$fname = $base.$fname;
214	}
215	for my $field (qw(symlink link size ts)) {  # md5
216		if (defined $result->{$field}) {
217			$state->error("User tried to define @#1 for #2",
218			    $field, $fname);
219		}
220	}
221	if (defined $self->{wtempname}) {
222		$fname = $self->{wtempname};
223	}
224	if (-l $fname) {
225		if (!defined $base) {
226			$state->error("special file #1 can't be a symlink",
227			    $self->stringize);
228		}
229		my $value = readlink $fname;
230		my $chk = resolve_link($fname, $base);
231		$fname =~ s|^//|/|; # cosmetic
232		if (!defined $chk) {
233			$state->error("bogus symlink: #1 (too deep)", $fname);
234		} elsif (!-e $chk) {
235			push(@{$state->{bad_symlinks}{$chk}}, $fname);
236		}
237		$result->make_symlink($value);
238	} elsif (-f _) {
239		my ($dev, $ino, $size, $mtime) = (stat _)[0,1,7, 9];
240		# XXX when rebuilding packages, tied updates can produce
241		# spurious hardlinks. We also refer to the installed plist
242		# we're rebuilding to know if we must checksum.
243		if (defined $state->stash("$dev/$ino") && !defined $self->{d}) {
244			$result->make_hardlink($state->stash("$dev/$ino"));
245		} else {
246			$state->{stash}{"$dev/$ino"} = $name;
247			$result->add_digest($self->compute_digest($fname))
248			    unless $state->{bad};
249			$result->add_size($size);
250			$result->add_timestamp($mtime);
251		}
252	} elsif (-d _) {
253		$state->error("#1 should be a file and not a directory", $fname);
254	} else {
255		$state->error("#1 does not exist", $fname);
256	}
257}
258
259sub makesum_plist_with_base
260{
261	my ($self, $plist, $state, $base) = @_;
262	$self->compute_checksum($self, $state, $base);
263	$self->add_object($plist);
264}
265
266sub verify_checksum_with_base
267{
268	my ($self, $state, $base) = @_;
269	my $check = ref($self)->new($self->name);
270	$self->compute_checksum($check, $state, $base);
271
272	for my $field (qw(symlink link size)) {  # md5
273		if ((defined $check->{$field} && defined $self->{$field} &&
274		    $check->{$field} ne $self->{$field}) ||
275		    (defined $check->{$field} xor defined $self->{$field})) {
276		    	$state->error("#1 inconsistency for #2",
277			    $field, $self->fullname);
278		}
279	}
280	if ((defined $check->{d} && defined $self->{d} &&
281	    !$check->{d}->equals($self->{d})) ||
282	    (defined $check->{d} xor defined $self->{d})) {
283	    	$state->error("checksum inconsistency for #1",
284		    $self->fullname);
285	}
286}
287
288
289sub prepare_for_archival
290{
291	my ($self, $state) = @_;
292
293	my $o = $state->{archive}->prepare_long($self);
294	if (!$o->verify_modes($self)) {
295		$state->error("modes don't match for #1", $self->fullname);
296	}
297	if (!$o->is_allowed) {
298		$state->error("can't package #1", $self->fullname);
299	}
300	return $o;
301}
302
303sub discover_directories
304{
305}
306
307sub check_version
308{
309}
310
311package OpenBSD::PackingElement::StreamMarker;
312our @ISA = qw(OpenBSD::PackingElement::Meta);
313sub new
314{
315	my $class = shift;
316	bless {}, $class;
317}
318
319sub comment_create_package
320{
321	my ($self, $state) = @_;
322	$self->SUPER::comment_create_package($state);
323	$state->say("Gzip: next chunk");
324}
325
326sub archive
327{
328	my ($self, $state) = @_;
329	$state->new_gstream;
330}
331
332package OpenBSD::PackingElement::Meta;
333sub record_digest
334{
335	my ($self, $original, $entries, $new, $tail) = @_;
336	push(@$new, $self);
337}
338
339package OpenBSD::PackingElement::RcScript;
340sub set_destdir
341{
342	my ($self, $state) = @_;
343	if ($self->name =~ m/^\//) {
344		$state->{archive}->destdir($state->{base});
345	} else {
346		$self->SUPER::set_destdir($state);
347	}
348}
349
350package OpenBSD::PackingElement::SpecialFile;
351sub archive
352{
353	&OpenBSD::PackingElement::FileBase::archive;
354}
355
356sub pretend_to_archive
357{
358	&OpenBSD::PackingElement::FileBase::pretend_to_archive;
359}
360
361sub set_destdir
362{
363}
364
365sub may_add
366{
367	my ($class, $subst, $plist, $opt) = @_;
368	if (defined $opt) {
369		my $o = $class->add($plist);
370		$subst->copy($opt, $o->fullname) if defined $o->fullname;
371	}
372}
373
374sub comment_create_package
375{
376	my ($self, $state) = @_;
377	$state->say("Adding #1", $self->name);
378}
379
380sub makesum_plist
381{
382	my ($self, $state, $plist) = @_;
383	$self->makesum_plist_with_base($plist, $state, undef);
384}
385
386sub verify_checksum
387{
388	my ($self, $state) = @_;
389	$self->verify_checksum_with_base($state, undef);
390}
391
392sub prepare_for_archival
393{
394	my ($self, $state) = @_;
395
396	my $o = $state->{archive}->prepare_long($self);
397	$o->{uname} = 'root';
398	$o->{gname} = 'wheel';
399	$o->{uid} = 0;
400	$o->{gid} = 0;
401	$o->{mode} &= 0555; # zap all write and suid modes
402	return $o;
403}
404
405sub forbidden() { 1 }
406
407# override for CONTENTS: we cannot checksum this.
408package OpenBSD::PackingElement::FCONTENTS;
409sub makesum_plist
410{
411}
412
413sub verify_checksum
414{
415}
416
417sub archive
418{
419	my ($self, $state) = @_;
420	$self->SUPER::archive($state);
421	$state->new_gstream;
422}
423
424sub comment_create_package
425{
426	my ($self, $state) = @_;
427	$self->SUPER::comment_create_package($state);
428	$state->say("GZIP: END OF SIGNATURE CHUNK");
429}
430
431package OpenBSD::PackingElement::Cwd;
432sub archive
433{
434	my ($self, $state) = @_;
435}
436
437sub pretend_to_archive
438{
439	my ($self, $state) = @_;
440	$self->comment_create_package($state);
441}
442
443sub comment_create_package
444{
445	my ($self, $state) = @_;
446	$state->say("Cwd: #1", $self->name);
447}
448
449package OpenBSD::PackingElement::FileBase;
450
451sub record_digest
452{
453	my ($self, $original, $entries, $new, $tail) = @_;
454	if (defined $self->{d}) {
455		my $k = $self->{d}->stringize;
456		push(@{$entries->{$k}}, $self);
457		push(@$original, $k);
458	} else {
459		push(@$tail, $self);
460	}
461}
462
463sub set_destdir
464{
465	my ($self, $state) = @_;
466
467	$state->{archive}->destdir($state->{base}."/".$self->cwd);
468}
469
470sub archive
471{
472	my ($self, $state) = @_;
473
474	$self->set_destdir($state);
475	my $o = $self->prepare_for_archival($state);
476
477	$o->write unless $state->{bad};
478}
479
480sub really_archived { 1 }
481sub pretend_to_archive
482{
483	my ($self, $state) = @_;
484
485	$self->set_destdir($state);
486	$self->prepare_for_archival($state);
487	$self->comment_create_package($state);
488}
489
490sub comment_create_package
491{
492	my ($self, $state) = @_;
493	$state->say("Adding #1", $self->name);
494}
495
496sub print_file
497{
498	my ($item) = @_;
499	print '@', $item->keyword, " ", $item->fullname, "\n";
500}
501
502sub makesum_plist
503{
504	my ($self, $state, $plist) = @_;
505	$self->makesum_plist_with_base($plist, $state, $state->{base});
506}
507
508sub verify_checksum
509{
510	my ($self, $state) = @_;
511	$self->verify_checksum_with_base($state, $state->{base});
512}
513
514package OpenBSD::PackingElement::Dir;
515sub discover_directories
516{
517	my ($self, $state) = @_;
518	$state->{known_dirs}->{$self->fullname} = 1;
519}
520
521package OpenBSD::PackingElement::InfoFile;
522sub makesum_plist
523{
524	my ($self, $state, $plist) = @_;
525	$self->SUPER::makesum_plist($state, $plist);
526	my $fname = $self->fullname;
527	for (my $i = 1; ; $i++) {
528		if (-e "$state->{base}/$fname-$i") {
529			my $e = OpenBSD::PackingElement::File->add($plist, $self->name."-".$i);
530			$e->compute_checksum($e, $state, $state->{base});
531		} else {
532			last;
533		}
534	}
535}
536
537package OpenBSD::PackingElement::Manpage;
538use File::Basename;
539
540sub grab_manpages
541{
542	my ($self, $state) = @_;
543	my $filename;
544	if ($self->{wtempname}) {
545		$filename = $self->{wtempname};
546	} else {
547		$filename = $state->{base}.$self->fullname;
548	}
549	push(@{$state->{manpages}}, $filename);
550}
551
552sub makesum_plist
553{
554	my ($self, $state, $plist) = @_;
555	if ($state->{subst}->empty("USE_GROFF") || !$self->is_source) {
556		return $self->SUPER::makesum_plist($state, $plist);
557	}
558	my $dest = $self->source_to_dest;
559	my $fullname = $self->cwd."/".$dest;
560	my $d = dirname($fullname);
561	$state->{mandir} //= OpenBSD::Temp::permanent_dir(
562	    $ENV{TMPDIR} // '/tmp', "manpage");
563	my $tempname = $state->{mandir}."/".$fullname;
564	require File::Path;
565	File::Path::make_path($state->{mandir}."/".$d);
566	open my $fh, ">", $tempname or $state->error("can't create #1: #2",
567	    $tempname, $!);
568	chmod 0444, $fh;
569	if (-d $state->{base}.$d) {
570		undef $d;
571	}
572	$self->format($state, $tempname, $fh) or return;
573	if (-z $tempname) {
574		$state->errsay("groff produced empty result for #1", $dest);
575		$state->errsay("\tkeeping source manpage");
576		return $self->SUPER::makesum_plist($state, $plist);
577	}
578	if (defined $d && !$state->{known_dirs}->{$d}) {
579		$state->{known_dirs}->{$d} = 1;
580		OpenBSD::PackingElement::Dir->add($plist, dirname($dest));
581	}
582	my $e = OpenBSD::PackingElement::Manpage->add($plist, $dest);
583	$e->{wtempname} = $tempname;
584	$e->compute_checksum($e, $state, $state->{base});
585}
586
587package OpenBSD::PackingElement::Depend;
588sub avert_duplicates_and_other_checks
589{
590	my ($self, $state) = @_;
591	if (!$self->spec->is_valid) {
592		$state->error("invalid \@#1 #2 in packing-list",
593		    $self->keyword, $self->stringize);
594	}
595	$self->SUPER::avert_duplicates_and_other_checks($state);
596}
597
598sub forbidden() { 1 }
599
600package OpenBSD::PackingElement::Conflict;
601sub avert_duplicates_and_other_checks
602{
603	$_[1]->{has_conflict}++;
604	&OpenBSD::PackingElement::Depend::avert_duplicates_and_other_checks;
605}
606
607package OpenBSD::PackingElement::AskUpdate;
608sub avert_duplicates_and_other_checks
609{
610	&OpenBSD::PackingElement::Depend::avert_duplicates_and_other_checks;
611}
612
613package OpenBSD::PackingElement::Dependency;
614sub avert_duplicates_and_other_checks
615{
616	my ($self, $state) = @_;
617
618	$self->SUPER::avert_duplicates_and_other_checks($state);
619
620	my @issues = OpenBSD::PackageName->from_string($self->{def})->has_issues;
621	if (@issues > 0) {
622		$state->error("\@#1 #2\n  #3, #4",
623		    $self->keyword, $self->stringize,
624		    $self->{def}, join(' ', @issues));
625	} elsif ($self->spec->is_valid) {
626		my @m = $self->spec->filter($self->{def});
627		if (@m == 0) {
628			$state->error("\@#1 #2\n  pattern #3 doesn't match default #4\n",
629			    $self->keyword, $self->stringize,
630			    $self->{pattern}, $self->{def});
631		}
632	}
633}
634
635package OpenBSD::PackingElement::Name;
636sub avert_duplicates_and_other_checks
637{
638	my ($self, $state) = @_;
639
640	my @issues = OpenBSD::PackageName->from_string($self->name)->has_issues;
641	if (@issues > 0) {
642		$state->error("bad package name #1: ", $self->name,
643		    join(' ', @issues));
644	}
645	$self->SUPER::avert_duplicates_and_other_checks($state);
646}
647
648sub forbidden() { 1 }
649
650package OpenBSD::PackingElement::NoDefaultConflict;
651sub avert_duplicates_and_other_checks
652{
653	my ($self, $state) = @_;
654	$state->{has_no_default_conflict}++;
655}
656
657
658package OpenBSD::PackingElement::Lib;
659sub check_version
660{
661	my ($self, $state, $unsubst) = @_;
662	my @l  = $self->parse($self->name);
663	if (defined $l[0]) {
664		if (!$unsubst =~ m/\$\{LIB$l[0]_VERSION\}/) {
665			$state->error("Incorrectly versioned shared library: #1", $unsubst);
666		}
667	} else {
668		$state->error("Invalid shared library #1", $unsubst);
669	}
670	$state->{has_libraries} = 1;
671}
672
673package OpenBSD::PackingElement::DigitalSignature;
674sub is_forbidden() { 1 }
675
676package OpenBSD::PackingElement::Signer;
677sub is_forbidden() { 1 }
678
679package OpenBSD::PackingElement::ExtraInfo;
680sub is_forbidden() { 1 }
681
682package OpenBSD::PackingElement::ManualInstallation;
683sub is_forbidden() { 1 }
684
685package OpenBSD::PackingElement::Firmware;
686sub is_forbidden() { 1 }
687
688package OpenBSD::PackingElement::Url;
689sub is_forbidden() { 1 }
690
691package OpenBSD::PackingElement::Arch;
692sub is_forbidden() { 1 }
693
694package OpenBSD::PackingElement::LocalBase;
695sub is_forbidden() { 1 }
696
697package OpenBSD::PackingElement::Fragment;
698our @ISA=qw(OpenBSD::PackingElement);
699
700sub needs_keyword() { 0 }
701
702sub stringize
703{
704	return '%%'.shift->{name}.'%%';
705}
706
707package OpenBSD::PackingElement::NoFragment;
708our @ISA=qw(OpenBSD::PackingElement::Fragment);
709sub stringize
710{
711	return '!%%'.shift->{name}.'%%';
712}
713
714# put together file and filename, in order to handle fragments simply
715package MyFile;
716sub new
717{
718	my ($class, $filename) = @_;
719
720	open(my $fh, '<', $filename) or die "Missing file $filename";
721
722	bless { fh => $fh, name => $filename }, (ref($class) || $class);
723}
724
725sub readline
726{
727	my $self = shift;
728	return readline $self->{fh};
729}
730
731sub name
732{
733	my $self = shift;
734	return $self->{name};
735}
736
737sub close
738{
739	my $self = shift;
740	close($self->{fh});
741}
742
743sub deduce_name
744{
745	my ($self, $frag, $not) = @_;
746
747	my $o = $self->name;
748	my $noto = $o;
749	my $nofrag = "no-$frag";
750
751	$o =~ s/PFRAG\./PFRAG.$frag-/o or
752	    $o =~ s/PLIST/PFRAG.$frag/o;
753
754	$noto =~ s/PFRAG\./PFRAG.no-$frag-/o or
755	    $noto =~ s/PLIST/PFRAG.no-$frag/o;
756	unless (-e $o or -e $noto) {
757		die "Missing fragments for $frag: $o and $noto don't exist";
758	}
759	if ($not) {
760		return $noto if -e $noto;
761    	} else {
762		return $o if -e $o;
763	}
764	return;
765}
766
767# special solver class for PkgCreate
768package OpenBSD::Dependencies::CreateSolver;
769our @ISA = qw(OpenBSD::Dependencies::SolverBase);
770
771# we need to "hack" a special set
772sub new
773{
774	my ($class, $plist) = @_;
775	bless { set => OpenBSD::PseudoSet->new($plist), bad => [] }, $class;
776}
777
778sub solve_all_depends
779{
780	my ($solver, $state) = @_;
781
782	while (1) {
783		my @todo = $solver->solve_depends($state);
784		if (@todo == 0) {
785			return;
786		}
787		if ($solver->solve_wantlibs($state, 0)) {
788			return;
789		}
790		$solver->{set}->add_new(@todo);
791	}
792}
793
794sub solve_wantlibs
795{
796	my ($solver, $state, $final) = @_;
797
798	my $okay = 1;
799	my $lib_finder = OpenBSD::lookup::library->new($solver);
800	my $h = $solver->{set}->{new}[0];
801	for my $lib (@{$h->{plist}->{wantlib}}) {
802		$solver->{localbase} = $h->{plist}->localbase;
803		next if $lib_finder->lookup($solver,
804		    $solver->{to_register}->{$h}, $state,
805		    $lib->spec);
806		$okay = 0;
807		OpenBSD::SharedLibs::report_problem($state,
808		    $lib->spec) if $final;
809	}
810	if (!$okay && $final) {
811		$solver->dump($state);
812		$lib_finder->dump($state);
813	}
814	return $okay;
815}
816
817sub really_solve_dependency
818{
819	my ($self, $state, $dep, $package) = @_;
820
821	$state->progress->message($dep->{pkgpath});
822
823	# look in installed packages
824	my $v = $self->find_dep_in_installed($state, $dep);
825	if (!defined $v) {
826		$v = $self->find_dep_in_self($state, $dep);
827	}
828
829	# and in portstree otherwise
830	if (!defined $v) {
831		$v = $self->solve_from_ports($state, $dep, $package);
832	}
833	return $v;
834}
835
836sub diskcachename
837{
838	my ($self, $dep) = @_;
839
840	if ($ENV{_DEPENDS_CACHE}) {
841		my $diskcache = $dep->{pkgpath};
842		$diskcache =~ s/\//--/g;
843		return $ENV{_DEPENDS_CACHE}."/pkgcreate-".$diskcache;
844	} else {
845		return undef;
846	}
847}
848
849sub to_cache
850{
851	my ($self, $plist, $final) = @_;
852	# try to cache atomically.
853	# no error if it doesn't work
854	require OpenBSD::MkTemp;
855	my ($fh, $tmp) = OpenBSD::MkTemp::mkstemp(
856	    "$ENV{_DEPENDS_CACHE}/my.XXXXXXXXXXX") or return;
857	chmod 0644, $fh;
858	$plist->write($fh);
859	close($fh);
860	rename($tmp, $final);
861	unlink($tmp);
862}
863
864sub ask_tree
865{
866	my ($self, $state, $dep, $portsdir, @action) = @_;
867
868	my $make = OpenBSD::Paths->make;
869	my $pid = open(my $fh, "-|");
870	if (!defined $pid) {
871		$state->fatal("cannot fork: $!");
872	}
873	if ($pid == 0) {
874		chdir $portsdir or exit 2;
875		open STDERR, '>', '/dev/null';
876		$ENV{FULLPATH} = 'Yes';
877		delete $ENV{FLAVOR};
878		delete $ENV{SUBPACKAGE};
879		$ENV{SUBDIR} = $dep->{pkgpath};
880		$ENV{ECHO_MSG} = ':';
881		exec $make ('make', @action);
882	}
883	my $plist = OpenBSD::PackingList->read($fh,
884	    \&OpenBSD::PackingList::PrelinkStuffOnly);
885	close($fh);
886	return $plist;
887}
888
889sub really_solve_from_ports
890{
891	my ($self, $state, $dep, $portsdir) = @_;
892
893	my $diskcache = $self->diskcachename($dep);
894	my $plist;
895
896	if (defined $diskcache && -f $diskcache) {
897		$plist = OpenBSD::PackingList->fromfile($diskcache);
898	} else {
899		$plist = $self->ask_tree($state, $dep, $portsdir,
900		    'print-plist-libs-with-depends',
901		    'wantlib_args=no-wantlib-args');
902		if ($? != 0 || !defined $plist->pkgname) {
903			return undef;
904		}
905		if (defined $diskcache) {
906			$self->to_cache($plist, $diskcache);
907		}
908	}
909	OpenBSD::SharedLibs::add_libs_from_plist($plist, $state);
910	$self->add_dep($plist);
911	return $plist->pkgname;
912}
913
914my $cache = {};
915
916sub solve_from_ports
917{
918	my ($self, $state, $dep, $package) = @_;
919
920	my $portsdir = $state->defines('PORTSDIR');
921	return undef unless defined $portsdir;
922	my $pkgname;
923	if (defined $cache->{$dep->{pkgpath}}) {
924		$pkgname = $cache->{$dep->{pkgpath}};
925	} else {
926		$pkgname = $self->really_solve_from_ports($state, $dep,
927		    $portsdir);
928		$cache->{$dep->{pkgpath}} = $pkgname;
929	}
930	if (!defined $pkgname) {
931		$state->error("Can't obtain dependency #1 from ports tree",
932		    $dep->{pattern});
933		return undef;
934	}
935	if ($dep->spec->filter($pkgname) == 0) {
936		$state->error("Dependency #1 doesn't match FULLPKGNAME: #2",
937		    $dep->{pattern}, $pkgname);
938		return undef;
939	}
940
941	return $pkgname;
942}
943
944# we don't want old libs
945sub find_old_lib
946{
947	return undef;
948}
949
950package OpenBSD::PseudoHandle;
951sub new
952{
953	my ($class, $plist) = @_;
954	bless { plist => $plist}, $class;
955}
956
957sub pkgname
958{
959	my $self = shift;
960
961	return $self->{plist}->pkgname;
962}
963
964sub dependency_info
965{
966	my $self = shift;
967	return $self->{plist};
968}
969
970package OpenBSD::PseudoSet;
971sub new
972{
973	my ($class, @elements) = @_;
974
975	my $o = bless {}, $class;
976	$o->add_new(@elements);
977}
978
979sub add_new
980{
981	my ($self, @elements) = @_;
982	for my $i (@elements) {
983		push(@{$self->{new}}, OpenBSD::PseudoHandle->new($i));
984	}
985	return $self;
986}
987
988sub newer
989{
990	return @{shift->{new}};
991}
992
993
994sub newer_names
995{
996	return map {$_->pkgname} @{shift->{new}};
997}
998
999sub older
1000{
1001	return ();
1002}
1003
1004sub older_names
1005{
1006	return ();
1007}
1008
1009sub kept
1010{
1011	return ();
1012}
1013
1014sub kept_names
1015{
1016	return ();
1017}
1018
1019sub print
1020{
1021	my $self = shift;
1022	return $self->{new}[0]->pkgname;
1023}
1024
1025package OpenBSD::PkgCreate;
1026our @ISA = qw(OpenBSD::AddCreateDelete);
1027
1028sub handle_fragment
1029{
1030	my ($self, $state, $old, $not, $frag, undef, $cont, $msg) = @_;
1031	my $def = $frag;
1032	if ($state->{subst}->has_fragment($def, $frag, $msg)) {
1033		return undef if defined $not;
1034	} else {
1035		return undef unless defined $not;
1036	}
1037	my $newname = $old->deduce_name($frag, $not);
1038	if (defined $newname) {
1039		$state->set_status("switching to $newname")
1040		    if !defined $state->opt('q');
1041		return $old->new($newname);
1042	}
1043	return undef;
1044}
1045
1046sub FileClass
1047{
1048	return "MyFile";
1049}
1050
1051sub read_fragments
1052{
1053	my ($self, $state, $plist, $filename) = @_;
1054
1055	my $stack = [];
1056	my $subst = $state->{subst};
1057	push(@$stack, $self->FileClass->new($filename));
1058	my $fast = $subst->value("LIBS_ONLY");
1059
1060	return $plist->read($stack,
1061	    sub {
1062		my ($stack, $cont) = @_;
1063		while(my $file = pop @$stack) {
1064			while (my $l = $file->readline) {
1065				$state->progress->working(2048) unless $state->opt('q');
1066				if ($l =~m/^(\@comment\s+\$(?:Open)BSD\$)$/o) {
1067					$l = '@comment $'.'OpenBSD: '.basename($file->name).',v$';
1068				}
1069				if ($l =~ m/^(\!)?\%\%(.*)\%\%$/) {
1070					if (my $f2 = $self->handle_fragment($state, $file, $1, $2, $l, $cont, $filename)) {
1071						push(@$stack, $file);
1072						$file = $f2;
1073					}
1074					next;
1075				}
1076				my $s = $subst->do($l);
1077				if ($fast) {
1078					next unless $s =~ m/^\@(?:cwd|lib|depend|wantlib)\b/o || $s =~ m/lib.*\.a$/o;
1079				}
1080	# XXX some things, like @comment no checksum, don't produce an object
1081				my $o = &$cont($s);
1082				if (defined $o) {
1083					$o->check_version($state, $s);
1084					$self->annotate($o, $l, $file);
1085				}
1086			}
1087		}
1088	    });
1089}
1090
1091sub annotate
1092{
1093}
1094
1095sub add_description
1096{
1097	my ($state, $plist, $name, $opt_d) = @_;
1098	my $o = OpenBSD::PackingElement::FDESC->add($plist, $name);
1099	my $subst = $state->{subst};
1100	my $comment = $subst->value('COMMENT');
1101	if (defined $comment) {
1102		if (length $comment > 60) {
1103			$state->fatal("comment is too long\n#1\n#2\n",
1104			    $comment, ' 'x60 . "^" x (length($comment)-60));
1105		}
1106	} else {
1107		$state->usage("Comment required");
1108	}
1109	if (!defined $opt_d) {
1110		$state->usage("Description required");
1111	}
1112	return if $state->opt('q');
1113
1114	open(my $fh, '>', $o->fullname) or die "Can't write to DESC: $!";
1115	if (defined $comment) {
1116		print $fh $subst->do($comment), "\n";
1117	}
1118	if ($opt_d =~ /^\-(.*)$/o) {
1119		print $fh $1, "\n";
1120	} else {
1121		$subst->copy_fh($opt_d, $fh);
1122	}
1123	if (defined $comment) {
1124		if ($subst->empty('MAINTAINER')) {
1125			$state->errsay("no MAINTAINER");
1126		} else {
1127			print $fh "\n",
1128			    $subst->do('Maintainer: ${MAINTAINER}'), "\n";
1129		}
1130		if (!$subst->empty('HOMEPAGE')) {
1131			print $fh "\n", $subst->do('WWW: ${HOMEPAGE}'), "\n";
1132		}
1133	}
1134	close($fh);
1135}
1136
1137sub add_extra_info
1138{
1139	my ($self, $plist, $state) = @_;
1140
1141	my $subst = $state->{subst};
1142	my $fullpkgpath = $subst->value('FULLPKGPATH');
1143	my $cdrom = $subst->value('PERMIT_PACKAGE_CDROM') ||
1144	    $subst->value('CDROM');;
1145	my $ftp = $subst->value('PERMIT_PACKAGE_FTP') ||
1146	    $subst->value('FTP');
1147	if (defined $fullpkgpath || defined $cdrom || defined $ftp) {
1148		$fullpkgpath //= '';
1149		$cdrom //= 'no';
1150		$ftp //= 'no';
1151		$cdrom = 'yes' if $cdrom =~ m/^yes$/io;
1152		$ftp = 'yes' if $ftp =~ m/^yes$/io;
1153
1154		OpenBSD::PackingElement::ExtraInfo->add($plist,
1155		    $fullpkgpath, $cdrom, $ftp);
1156	} else {
1157		$state->errsay("Package without FULLPKGPATH");
1158	}
1159}
1160
1161sub add_elements
1162{
1163	my ($self, $plist, $state) = @_;
1164
1165	my $subst = $state->{subst};
1166	add_description($state, $plist, DESC, $state->opt('d'));
1167	OpenBSD::PackingElement::FDISPLAY->may_add($subst, $plist,
1168	    $state->opt('M'));
1169	OpenBSD::PackingElement::FUNDISPLAY->may_add($subst, $plist,
1170	    $state->opt('U'));
1171	for my $d (sort keys %{$state->{dependencies}}) {
1172		OpenBSD::PackingElement::Dependency->add($plist, $d);
1173	}
1174
1175	for my $w (sort keys %{$state->{wantlib}}) {
1176		OpenBSD::PackingElement::Wantlib->add($plist, $w);
1177	}
1178
1179	if (defined $state->opt('A')) {
1180		OpenBSD::PackingElement::Arch->add($plist, $state->opt('A'));
1181	}
1182
1183	if (defined $state->opt('L')) {
1184		OpenBSD::PackingElement::LocalBase->add($plist, $state->opt('L'));
1185	}
1186	$self->add_extra_info($plist, $state);
1187}
1188
1189sub cant_read_fragment
1190{
1191	my ($self, $state, $frag) = @_;
1192	$state->fatal("can't read packing-list #1", $frag);
1193}
1194
1195sub read_all_fragments
1196{
1197	my ($self, $state, $plist) = @_;
1198
1199	if (defined $state->{prefix}) {
1200		OpenBSD::PackingElement::Cwd->add($plist, $state->{prefix});
1201	} else {
1202		$state->usage("Prefix required");
1203	}
1204	for my $contentsfile (@{$state->{contents}}) {
1205		$self->read_fragments($state, $plist, $contentsfile) or
1206		    $self->cant_read_fragment($state, $contentsfile);
1207	}
1208
1209	$plist->register_forbidden($state);
1210	if (defined $state->{forbidden}) {
1211		for my $e (@{$state->{forbidden}}) {
1212			$state->errsay("Error: #1 can't be set explicitly", "\@".$e->keyword." ".$e->stringize);
1213		}
1214		$state->fatal("Can't continue");
1215	}
1216}
1217
1218sub create_plist
1219{
1220	my ($self, $state, $pkgname) = @_;
1221
1222	my $plist = OpenBSD::PackingList->new;
1223
1224	if ($pkgname =~ m|([^/]+)$|o) {
1225		$pkgname = $1;
1226		$pkgname =~ s/\.tgz$//o;
1227	}
1228	$state->say("Creating package #1", $pkgname)
1229	    if !(defined $state->opt('q')) && $state->opt('v');
1230	if (!$state->opt('q')) {
1231		$plist->set_infodir(OpenBSD::Temp->dir);
1232	}
1233
1234	unless (defined $state->opt('q') && defined $state->opt('n')) {
1235		$state->set_status("reading plist");
1236	}
1237	$self->read_all_fragments($state, $plist);
1238	$plist->set_pkgname($pkgname);
1239
1240	$self->add_elements($plist, $state);
1241	return $plist;
1242}
1243
1244sub make_plist_with_sum
1245{
1246	my ($self, $state, $plist) = @_;
1247	my $p2 = OpenBSD::PackingList->new;
1248	$state->progress->visit_with_count($plist, 'makesum_plist', $p2);
1249	$p2->set_infodir($plist->infodir);
1250	return $p2;
1251}
1252
1253sub read_existing_plist
1254{
1255	my ($self, $state, $contents) = @_;
1256
1257	my $plist = OpenBSD::PackingList->new;
1258	if (-d $contents && -f $contents.'/'.CONTENTS) {
1259		$plist->set_infodir($contents);
1260		$contents .= '/'.CONTENTS;
1261	} else {
1262		$plist->set_infodir(dirname($contents));
1263	}
1264	$plist->fromfile($contents) or
1265	    $state->fatal("can't read packing-list #1", $contents);
1266	return $plist;
1267}
1268
1269sub create_package
1270{
1271	my ($self, $state, $plist, $ordered, $wname) = @_;
1272
1273	$state->say("Creating gzip'd tar ball in '#1'", $wname)
1274	    if $state->opt('v');
1275	my $h = sub {
1276		unlink $wname;
1277		my $caught = shift;
1278		$SIG{$caught} = 'DEFAULT';
1279		kill $caught, $$;
1280	};
1281
1282	local $SIG{'INT'} = $h;
1283	local $SIG{'QUIT'} = $h;
1284	local $SIG{'HUP'} = $h;
1285	local $SIG{'KILL'} = $h;
1286	local $SIG{'TERM'} = $h;
1287	$state->{archive} = $state->create_archive($wname, $plist->infodir);
1288	$state->set_status("archiving");
1289	my $p = $state->progress->new_sizer($plist, $state);
1290	for my $e (@$ordered) {
1291		$e->create_package($state);
1292		$p->advance($e);
1293	}
1294	$state->end_status;
1295	$state->{archive}->close;
1296	if ($state->{bad}) {
1297		unlink($wname);
1298		exit(1);
1299	}
1300}
1301
1302sub show_bad_symlinks
1303{
1304	my ($self, $state) = @_;
1305	for my $dest (sort keys %{$state->{bad_symlinks}}) {
1306		$state->errsay("Warning: symlink(s) point to non-existent #1",
1307		    $dest);
1308		for my $link (@{$state->{bad_symlinks}{$dest}}) {
1309			$state->errsay("\t#1", $link);
1310		}
1311	}
1312}
1313
1314sub check_dependencies
1315{
1316	my ($self, $plist, $state) = @_;
1317
1318	my $solver = OpenBSD::Dependencies::CreateSolver->new($plist);
1319
1320	# look for libraries in the "real" tree
1321	$state->{destdir} = '/';
1322
1323	$solver->solve_all_depends($state);
1324	if (!$solver->solve_wantlibs($state, 1)) {
1325		$state->{bad}++;
1326	}
1327}
1328
1329sub finish_manpages
1330{
1331	my ($self, $state, $plist) = @_;
1332	$plist->grab_manpages($state);
1333	if (defined $state->{manpages}) {
1334		$state->run_makewhatis(['-t'], $state->{manpages});
1335	}
1336
1337	if (defined $state->{mandir}) {
1338		require File::Path;
1339		File::Path::remove_tree($state->{mandir});
1340	}
1341}
1342
1343sub save_history
1344{
1345	my ($self, $plist, $dir) = @_;
1346
1347	# grab the old stuff:
1348	# - order
1349	# - and presence
1350	my (%known, %found);
1351	my $fname;
1352	if (defined $dir) {
1353		unless (-d $dir) {
1354			require File::Path;
1355
1356			File::Path::make_path($dir);
1357		}
1358
1359		my $name = $plist->fullpkgpath;
1360		$name =~ s,/,.,g;
1361		my $fname = "$dir/$name";
1362		my $n = 0;
1363
1364		if (open(my $f, '<', $fname)) {
1365			while (<$f>) {
1366				chomp;
1367				$known{$_} //= $n++;
1368			}
1369			close($f);
1370		}
1371	}
1372	my @new;
1373	my $entries = {};
1374	my $list = [];
1375	my $tail = [];
1376	$plist->record_digest(\@new, $entries, $list, $tail);
1377
1378	my $f;
1379	if (defined $fname) {
1380		open($f, ">", "$fname.new");
1381	}
1382
1383	# split list
1384	# - first, unknown stuff
1385	for my $h (@new) {
1386		if ($known{$h}) {
1387			$found{$h} = $known{$h};
1388		} else {
1389			print $f "$h\n" if defined $f;
1390			push(@$list, (shift @{$entries->{$h}}));
1391		}
1392	}
1393	# - then known stuff, preserve the order
1394	for my $h (sort  {$found{$a} <=> $found{$b}} keys %found) {
1395		print $f "$h\n" if defined $f;
1396		push(@$list, @{$entries->{$h}});
1397	}
1398	if (defined $f) {
1399		close($f);
1400		rename("$fname.new", $fname);
1401	}
1402	# create a new list with check points.
1403	my $l = [@$tail];
1404	my $i = 0;
1405	my $end_marker = OpenBSD::PackingElement::StreamMarker->new;
1406	while (@$list > 0) {
1407		my $e = pop @$list;
1408		if ($e->really_archived && $i++ % 16 == 0) {
1409			unshift @$l, $end_marker;
1410		}
1411		unshift @$l, $e;
1412	}
1413	# remove extraneous marker if @$tail is empty.
1414	if ($l->[-1] eq $end_marker) {
1415		pop @$l;
1416	}
1417	return $l;
1418}
1419
1420sub parse_and_run
1421{
1422	my ($self, $cmd) = @_;
1423
1424	my $regen_package = 0;
1425	my $sign_only = 0;
1426
1427	my $state = OpenBSD::PkgCreate::State->new($cmd);
1428	$state->handle_options;
1429
1430	if (@ARGV == 0) {
1431		$regen_package = 1;
1432	} elsif (@ARGV != 1) {
1433		if (defined $state->{contents} ||
1434		    !defined $state->{signature_params}) {
1435			$state->usage("Exactly one single package name is required: #1", join(' ', @ARGV));
1436		}
1437	}
1438
1439	try {
1440	if (defined $state->opt('Q')) {
1441		$state->{opt}{q} = 1;
1442	}
1443
1444	if (!defined $state->{contents}) {
1445		$state->usage("Packing-list required");
1446	}
1447
1448	my $plist;
1449	if ($regen_package) {
1450		if (!defined $state->{contents} || @{$state->{contents}} > 1) {
1451			$state->usage("Exactly one single packing-list is required");
1452		}
1453		$plist = $self->read_existing_plist($state,
1454		    $state->{contents}[0]);
1455	} else {
1456		$plist = $self->create_plist($state, $ARGV[0]);
1457	}
1458
1459
1460	$plist->discover_directories($state);
1461	my $ordered;
1462	unless (defined $state->opt('q') && defined $state->opt('n')) {
1463		$state->set_status("checking dependencies");
1464		$self->check_dependencies($plist, $state);
1465		$state->set_status("checksumming");
1466		if ($regen_package) {
1467			$state->progress->visit_with_count($plist, 'verify_checksum');
1468		} else {
1469			$plist = $self->make_plist_with_sum($state, $plist);
1470		}
1471		$ordered = $self->save_history($plist,
1472		    $state->defines('HISTORY_DIR'));
1473		$self->show_bad_symlinks($state);
1474		$state->end_status;
1475	}
1476
1477	if (!defined $plist->pkgname) {
1478		$state->fatal("can't write unnamed packing-list");
1479	}
1480
1481	if (defined $state->opt('q')) {
1482		if (defined $state->opt('Q')) {
1483			$plist->print_file;
1484		} else {
1485			$plist->write(\*STDOUT);
1486		}
1487		return 0 if defined $state->opt('n');
1488	}
1489
1490	if ($plist->{deprecated}) {
1491		$state->fatal("found obsolete constructs");
1492	}
1493
1494	$plist->avert_duplicates_and_other_checks($state);
1495	if ($state->{has_no_default_conflict} && !$state->{has_conflict}) {
1496		$state->errsay("Warning: \@option no-default-conflict without \@conflict");
1497	}
1498	$state->{stash} = {};
1499
1500	if ($state->{bad} && !$state->defines('REGRESSION_TESTING')) {
1501		$state->fatal("can't continue");
1502	}
1503	$state->{bad} = 0;
1504
1505	if (defined $state->{signer}) {
1506		$state->add_signature($plist);
1507		$plist->save if $regen_package;
1508	}
1509
1510	my $wname;
1511	if ($regen_package) {
1512		$wname = $plist->pkgname.".tgz";
1513	} else {
1514		$plist->save or $state->fatal("can't write packing-list");
1515		$wname = $ARGV[0];
1516	}
1517
1518	if ($state->opt('n')) {
1519		$state->{archive} = OpenBSD::Ustar->new(undef, $state,
1520		    $plist->infodir);
1521		$plist->pretend_to_archive($state);
1522	} else {
1523		$self->create_package($state, $plist, $ordered, $wname);
1524	}
1525	$self->finish_manpages($state, $plist);
1526	}catch {
1527		print STDERR "$0: $_\n";
1528		return 1;
1529	};
1530	return 0;
1531}
1532
15331;
1534