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